From 555c0fae9658417485a4ed267a409cfbf919f214 Mon Sep 17 00:00:00 2001 From: satvu Date: Tue, 22 Apr 2025 16:28:59 -0700 Subject: [PATCH 01/16] wip --- .../FunctionInvocationMiddleware.cs | 13 +++++-- src/WebJobs.Script/WebJobs.Script.csproj | 1 + .../Http/Configuration/HttpWorkerOptions.cs | 4 +++ .../Configuration/HttpWorkerOptionsSetup.cs | 2 +- .../Workers/Http/DefaultHttpWorkerService.cs | 36 +++++++++++++++++-- .../DefaultHttpProxyService.cs | 3 +- .../HttpForwardingException.cs | 2 +- .../HttpProxyService}/IHttpProxyService.cs | 2 +- .../HttpProxyService}/RetryProxyHandler.cs | 2 +- 9 files changed, 54 insertions(+), 11 deletions(-) rename src/{WebJobs.Script.Grpc/Server => WebJobs.Script/Workers/Http/HttpProxyService}/DefaultHttpProxyService.cs (97%) rename src/{WebJobs.Script.Grpc/Exceptions => WebJobs.Script/Workers/Http/HttpProxyService}/HttpForwardingException.cs (90%) rename src/{WebJobs.Script.Grpc/Server => WebJobs.Script/Workers/Http/HttpProxyService}/IHttpProxyService.cs (94%) rename src/{WebJobs.Script.Grpc/Server => WebJobs.Script/Workers/Http/HttpProxyService}/RetryProxyHandler.cs (98%) diff --git a/src/WebJobs.Script.WebHost/Middleware/FunctionInvocationMiddleware.cs b/src/WebJobs.Script.WebHost/Middleware/FunctionInvocationMiddleware.cs index 2d4ff1b23f..0cd3fb9fda 100644 --- a/src/WebJobs.Script.WebHost/Middleware/FunctionInvocationMiddleware.cs +++ b/src/WebJobs.Script.WebHost/Middleware/FunctionInvocationMiddleware.cs @@ -57,9 +57,18 @@ public async Task Invoke(HttpContext context) int nestedProxiesCount = GetNestedProxiesCount(context, functionExecution); IActionResult result = await GetResultAsync(context, functionExecution); - if (context.Items.TryGetValue(ScriptConstants.HttpProxyingEnabled, out var value)) + if (context.Items.TryGetValue(ScriptConstants.HttpProxyingEnabled, out var httpProxyingEnabled)) { - if (value?.ToString() == bool.TrueString) + if (httpProxyingEnabled?.ToString() == bool.TrueString) + { + return; + } + } + + // TODO: Come back to this after custom handler proxying settings are set up + if (context.Items.TryGetValue("SomeValue", out var customHandlerProxyingEnabled)) + { + if (customHandlerProxyingEnabled?.ToString() == bool.TrueString) { return; } diff --git a/src/WebJobs.Script/WebJobs.Script.csproj b/src/WebJobs.Script/WebJobs.Script.csproj index bcccab5b9c..a13559ae82 100644 --- a/src/WebJobs.Script/WebJobs.Script.csproj +++ b/src/WebJobs.Script/WebJobs.Script.csproj @@ -73,6 +73,7 @@ + diff --git a/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptions.cs b/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptions.cs index efd253febc..cdf9762ab1 100644 --- a/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptions.cs +++ b/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptions.cs @@ -15,8 +15,12 @@ public class HttpWorkerOptions public int Port { get; set; } + // This is the legacy setting that rebuilds the HTTP request for forwrding public bool EnableForwardingHttpRequest { get; set; } + // This uses YARP to forward the HTTP request + public bool EnableProxyingHttpRequest { get; set; } + public TimeSpan InitializationTimeout { get; set; } = TimeSpan.FromSeconds(30); } } diff --git a/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptionsSetup.cs b/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptionsSetup.cs index c2817d291e..ad9b37a0a9 100644 --- a/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptionsSetup.cs +++ b/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptionsSetup.cs @@ -46,7 +46,7 @@ public void Configure(HttpWorkerOptions options) ConfigureWorkerDescription(options, customHandlerSection); if (options.Type == CustomHandlerType.None) { - // CustomHandlerType.None is only for maitaining backward compatibilty with httpWorker section. + // CustomHandlerType.None is only for maintaining backward compatibilty with httpWorker section. _logger.LogWarning($"CustomHandlerType {CustomHandlerType.None} is not supported. Defaulting to {CustomHandlerType.Http}."); options.Type = CustomHandlerType.Http; } diff --git a/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs b/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs index 825a8c24c7..8c28041223 100644 --- a/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs +++ b/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs @@ -24,17 +24,21 @@ public class DefaultHttpWorkerService : IHttpWorkerService private readonly HttpWorkerOptions _httpWorkerOptions; private readonly ILogger _logger; private readonly bool _enableRequestTracing; + private readonly IHttpProxyService _httpProxyService; - public DefaultHttpWorkerService(IOptions httpWorkerOptions, ILoggerFactory loggerFactory, IEnvironment environment, IOptions scriptHostOptions) - : this(CreateHttpClient(httpWorkerOptions), httpWorkerOptions, loggerFactory.CreateLogger(), environment, scriptHostOptions) + public DefaultHttpWorkerService(IOptions httpWorkerOptions, ILoggerFactory loggerFactory, IEnvironment environment, + IOptions scriptHostOptions, IHttpProxyService httpProxyService) + : this(CreateHttpClient(httpWorkerOptions), httpWorkerOptions, loggerFactory.CreateLogger(), environment, scriptHostOptions, httpProxyService) { } - internal DefaultHttpWorkerService(HttpClient httpClient, IOptions httpWorkerOptions, ILogger logger, IEnvironment environment, IOptions scriptHostOptions) + internal DefaultHttpWorkerService(HttpClient httpClient, IOptions httpWorkerOptions, ILogger logger, IEnvironment environment, + IOptions scriptHostOptions, IHttpProxyService httpProxyService) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); _httpWorkerOptions = httpWorkerOptions.Value ?? throw new ArgumentNullException(nameof(httpWorkerOptions.Value)); + _httpProxyService = httpProxyService ?? throw new ArgumentNullException(nameof(httpProxyService)); _enableRequestTracing = environment.IsCoreTools(); if (scriptHostOptions.Value.FunctionTimeout == null) { @@ -61,6 +65,11 @@ public Task InvokeAsync(ScriptInvocationContext scriptInvocationContext) { if (scriptInvocationContext.FunctionMetadata.IsHttpInAndOutFunction()) { + if (_httpWorkerOptions.EnableProxyingHttpRequest) + { + return ProxyInvocationRequest(scriptInvocationContext); + } + // type is empty for httpWorker section. EnableForwardingHttpRequest is opt-in for custom handler section. if (_httpWorkerOptions.Type == CustomHandlerType.None || _httpWorkerOptions.EnableForwardingHttpRequest) { @@ -71,6 +80,27 @@ public Task InvokeAsync(ScriptInvocationContext scriptInvocationContext) return ProcessDefaultInvocationRequest(scriptInvocationContext); } + internal async Task ProxyInvocationRequest(ScriptInvocationContext scriptInvocationContext) + { + var uriString = BuildAndGetUri(scriptInvocationContext.FunctionMetadata.Name); + var uri = new UriBuilder(uriString).Uri; + + try + { + _httpProxyService.StartForwarding(scriptInvocationContext, uri); + + await _httpProxyService.EnsureSuccessfulForwardingAsync(scriptInvocationContext); + + var result = new ScriptInvocationResult(); + + scriptInvocationContext.ResultSource.SetResult(result); + } + catch (Exception exc) + { + scriptInvocationContext.ResultSource.TrySetException(exc); + } + } + internal async Task ProcessHttpInAndOutInvocationRequest(ScriptInvocationContext scriptInvocationContext) { _logger.CustomHandlerForwardingHttpTriggerInvocation(scriptInvocationContext.FunctionMetadata.Name, scriptInvocationContext.ExecutionContext.InvocationId); diff --git a/src/WebJobs.Script.Grpc/Server/DefaultHttpProxyService.cs b/src/WebJobs.Script/Workers/Http/HttpProxyService/DefaultHttpProxyService.cs similarity index 97% rename from src/WebJobs.Script.Grpc/Server/DefaultHttpProxyService.cs rename to src/WebJobs.Script/Workers/Http/HttpProxyService/DefaultHttpProxyService.cs index 1d164c2c04..65a6db1cfd 100644 --- a/src/WebJobs.Script.Grpc/Server/DefaultHttpProxyService.cs +++ b/src/WebJobs.Script/Workers/Http/HttpProxyService/DefaultHttpProxyService.cs @@ -7,12 +7,11 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Azure.WebJobs.Script.Description; -using Microsoft.Azure.WebJobs.Script.Grpc.Exceptions; using Microsoft.Azure.WebJobs.Script.Workers; using Microsoft.Extensions.Logging; using Yarp.ReverseProxy.Forwarder; -namespace Microsoft.Azure.WebJobs.Script.Grpc +namespace Microsoft.Azure.WebJobs.Script { internal class DefaultHttpProxyService : IHttpProxyService, IDisposable { diff --git a/src/WebJobs.Script.Grpc/Exceptions/HttpForwardingException.cs b/src/WebJobs.Script/Workers/Http/HttpProxyService/HttpForwardingException.cs similarity index 90% rename from src/WebJobs.Script.Grpc/Exceptions/HttpForwardingException.cs rename to src/WebJobs.Script/Workers/Http/HttpProxyService/HttpForwardingException.cs index b60daba49d..3dbba3bb64 100644 --- a/src/WebJobs.Script.Grpc/Exceptions/HttpForwardingException.cs +++ b/src/WebJobs.Script/Workers/Http/HttpProxyService/HttpForwardingException.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Azure.WebJobs.Script.Grpc.Exceptions +namespace Microsoft.Azure.WebJobs.Script { internal class HttpForwardingException : Exception { diff --git a/src/WebJobs.Script.Grpc/Server/IHttpProxyService.cs b/src/WebJobs.Script/Workers/Http/HttpProxyService/IHttpProxyService.cs similarity index 94% rename from src/WebJobs.Script.Grpc/Server/IHttpProxyService.cs rename to src/WebJobs.Script/Workers/Http/HttpProxyService/IHttpProxyService.cs index 7db2eeaf12..6c820ed86d 100644 --- a/src/WebJobs.Script.Grpc/Server/IHttpProxyService.cs +++ b/src/WebJobs.Script/Workers/Http/HttpProxyService/IHttpProxyService.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Microsoft.Azure.WebJobs.Script.Description; -namespace Microsoft.Azure.WebJobs.Script.Grpc +namespace Microsoft.Azure.WebJobs.Script { public interface IHttpProxyService { diff --git a/src/WebJobs.Script.Grpc/Server/RetryProxyHandler.cs b/src/WebJobs.Script/Workers/Http/HttpProxyService/RetryProxyHandler.cs similarity index 98% rename from src/WebJobs.Script.Grpc/Server/RetryProxyHandler.cs rename to src/WebJobs.Script/Workers/Http/HttpProxyService/RetryProxyHandler.cs index 8659a0403b..011fe39bb9 100644 --- a/src/WebJobs.Script.Grpc/Server/RetryProxyHandler.cs +++ b/src/WebJobs.Script/Workers/Http/HttpProxyService/RetryProxyHandler.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; -namespace Microsoft.Azure.WebJobs.Script.Grpc +namespace Microsoft.Azure.WebJobs.Script { internal sealed class RetryProxyHandler : DelegatingHandler { From 6eac80d57d66ff7416a142bf73a2e0a5745ea1e8 Mon Sep 17 00:00:00 2001 From: satvu Date: Wed, 23 Apr 2025 13:29:18 -0700 Subject: [PATCH 02/16] refactor, add headers logic --- .../DefaultHttpProxyService.cs | 0 .../HttpForwardingException.cs | 0 .../HttpProxyService/IHttpProxyService.cs | 0 .../HttpProxyService/RetryProxyHandler.cs | 0 .../Workers/Http/DefaultHttpWorkerService.cs | 22 ++++++++++- .../DefaultHttpWorkerServiceTests.cs | 38 ++++++++++--------- 6 files changed, 41 insertions(+), 19 deletions(-) rename src/WebJobs.Script/{Workers/Http => }/HttpProxyService/DefaultHttpProxyService.cs (100%) rename src/WebJobs.Script/{Workers/Http => }/HttpProxyService/HttpForwardingException.cs (100%) rename src/WebJobs.Script/{Workers/Http => }/HttpProxyService/IHttpProxyService.cs (100%) rename src/WebJobs.Script/{Workers/Http => }/HttpProxyService/RetryProxyHandler.cs (100%) diff --git a/src/WebJobs.Script/Workers/Http/HttpProxyService/DefaultHttpProxyService.cs b/src/WebJobs.Script/HttpProxyService/DefaultHttpProxyService.cs similarity index 100% rename from src/WebJobs.Script/Workers/Http/HttpProxyService/DefaultHttpProxyService.cs rename to src/WebJobs.Script/HttpProxyService/DefaultHttpProxyService.cs diff --git a/src/WebJobs.Script/Workers/Http/HttpProxyService/HttpForwardingException.cs b/src/WebJobs.Script/HttpProxyService/HttpForwardingException.cs similarity index 100% rename from src/WebJobs.Script/Workers/Http/HttpProxyService/HttpForwardingException.cs rename to src/WebJobs.Script/HttpProxyService/HttpForwardingException.cs diff --git a/src/WebJobs.Script/Workers/Http/HttpProxyService/IHttpProxyService.cs b/src/WebJobs.Script/HttpProxyService/IHttpProxyService.cs similarity index 100% rename from src/WebJobs.Script/Workers/Http/HttpProxyService/IHttpProxyService.cs rename to src/WebJobs.Script/HttpProxyService/IHttpProxyService.cs diff --git a/src/WebJobs.Script/Workers/Http/HttpProxyService/RetryProxyHandler.cs b/src/WebJobs.Script/HttpProxyService/RetryProxyHandler.cs similarity index 100% rename from src/WebJobs.Script/Workers/Http/HttpProxyService/RetryProxyHandler.cs rename to src/WebJobs.Script/HttpProxyService/RetryProxyHandler.cs diff --git a/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs b/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs index 8c28041223..b4693389ec 100644 --- a/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs +++ b/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs @@ -87,12 +87,15 @@ internal async Task ProxyInvocationRequest(ScriptInvocationContext scriptInvocat try { + var input = scriptInvocationContext.Inputs.First(); + HttpRequest httpRequest = input.Val as HttpRequest; + AddProxyingHeaders(httpRequest, scriptInvocationContext.ExecutionContext.InvocationId.ToString()); + _httpProxyService.StartForwarding(scriptInvocationContext, uri); await _httpProxyService.EnsureSuccessfulForwardingAsync(scriptInvocationContext); var result = new ScriptInvocationResult(); - scriptInvocationContext.ResultSource.SetResult(result); } catch (Exception exc) @@ -195,6 +198,23 @@ internal void AddHeaders(HttpRequestMessage httpRequest, string invocationId) httpRequest.Headers.UserAgent.ParseAdd($"{HttpWorkerConstants.UserAgentHeaderValue}/{ScriptHost.Version}"); } + internal void AddProxyingHeaders(HttpRequest httpRequest, string invocationId) + { + httpRequest.Headers.TryAdd(HttpWorkerConstants.HostVersionHeaderName, ScriptHost.Version); + + if (!httpRequest.Headers.TryAdd(HttpWorkerConstants.InvocationIdHeaderName, invocationId)) + { + httpRequest.Headers[HttpWorkerConstants.InvocationIdHeaderName] = invocationId; + } + + var userAgent = $"{HttpWorkerConstants.UserAgentHeaderValue}/{ScriptHost.Version}"; + httpRequest.Headers.Remove("User-Agent"); + httpRequest.Headers.Append("User-Agent", userAgent); + + // Add header so that the functions middleware skips the script invocation result handling + httpRequest.Headers.TryAdd(ScriptConstants.HttpProxyingEnabled, bool.TrueString); // placeholder http proxying enabled header for now + } + internal string GetPathValue(HttpWorkerOptions httpWorkerOptions, string functionName, HttpRequest httpRequest) { string pathValue = functionName; diff --git a/test/WebJobs.Script.Tests/HttpWorker/DefaultHttpWorkerServiceTests.cs b/test/WebJobs.Script.Tests/HttpWorker/DefaultHttpWorkerServiceTests.cs index 66435a166c..001d42612d 100644 --- a/test/WebJobs.Script.Tests/HttpWorker/DefaultHttpWorkerServiceTests.cs +++ b/test/WebJobs.Script.Tests/HttpWorker/DefaultHttpWorkerServiceTests.cs @@ -39,6 +39,7 @@ public class DefaultHttpWorkerServiceTests private int _defaultPort = 8090; private TestLogger _testLogger = new TestLogger("ServiceLogger"); private TestLogger _functionLogger = new TestLogger(TestFunctionName); + private Mock mockHttpProxyService; public DefaultHttpWorkerServiceTests() { @@ -54,6 +55,7 @@ public DefaultHttpWorkerServiceTests() { FunctionTimeout = TimeSpan.FromMinutes(15) }; + mockHttpProxyService = new Mock(MockBehavior.Strict); } public static IEnumerable TestLogs @@ -77,7 +79,7 @@ public async Task ProcessDefaultInvocationRequest_Succeeds() .ReturnsAsync(HttpWorkerTestUtilities.GetValidHttpResponseMessage()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions)); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); Assert.Equal(_httpClient.Timeout, _scriptJobHostOptions.FunctionTimeout.Value.Add(TimeSpan.FromMinutes(1))); var testScriptInvocationContext = HttpWorkerTestUtilities.GetScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger); await _defaultHttpWorkerService.ProcessDefaultInvocationRequest(testScriptInvocationContext); @@ -108,7 +110,7 @@ public async Task ProcessDefaultInvocationRequest_CustomHandler_EnableRequestFor .ReturnsAsync(HttpWorkerTestUtilities.GetValidHttpResponseMessageWithJsonRes()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(customHandlerOptions), _testLogger, _testEnvironment, new OptionsWrapper(scriptJobHostOptionsNoTimeout)); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(customHandlerOptions), _testLogger, _testEnvironment, new OptionsWrapper(scriptJobHostOptionsNoTimeout), mockHttpProxyService.Object); Assert.Equal(_httpClient.Timeout, TimeSpan.FromMilliseconds(int.MaxValue)); var testScriptInvocationContext = HttpWorkerTestUtilities.GetSimpleHttpTriggerScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger); await _defaultHttpWorkerService.InvokeAsync(testScriptInvocationContext); @@ -140,7 +142,7 @@ public async Task ProcessDefaultInvocationRequest_DataType_Binary_Succeeds() .ReturnsAsync(HttpWorkerTestUtilities.GetValidHttpResponseMessage_DataType_Binary_Data()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions)); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger, WebJobs.Script.Description.DataType.Binary); await _defaultHttpWorkerService.ProcessDefaultInvocationRequest(testScriptInvocationContext); var invocationResult = await testScriptInvocationContext.ResultSource.Task; @@ -176,7 +178,7 @@ public async Task ProcessDefaultInvocationRequest_BinaryData_Succeeds() .ReturnsAsync(HttpWorkerTestUtilities.GetValidHttpResponseMessage_Binary_Data()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions)); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger); await _defaultHttpWorkerService.ProcessDefaultInvocationRequest(testScriptInvocationContext); var invocationResult = await testScriptInvocationContext.ResultSource.Task; @@ -214,7 +216,7 @@ public async Task ProcessPing_Succeeds() .ReturnsAsync(HttpWorkerTestUtilities.GetSimpleNotFoundHttpResponseMessage()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions)); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); await _defaultHttpWorkerService.PingAsync(); handlerMock.VerifyAll(); } @@ -231,7 +233,7 @@ public async Task ProcessSimpleHttpTriggerInvocationRequest_Succeeds() .ReturnsAsync(HttpWorkerTestUtilities.GetValidSimpleHttpResponseMessage()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions)); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetSimpleHttpTriggerScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger); await _defaultHttpWorkerService.ProcessHttpInAndOutInvocationRequest(testScriptInvocationContext); var invocationResult = await testScriptInvocationContext.ResultSource.Task; @@ -269,7 +271,7 @@ public async Task ProcessSimpleHttpTriggerInvocationRequest_CustomHandler_Enable .ReturnsAsync(HttpWorkerTestUtilities.GetValidSimpleHttpResponseMessage()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(customHandlerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions)); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(customHandlerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetSimpleHttpTriggerScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger); await _defaultHttpWorkerService.InvokeAsync(testScriptInvocationContext); var invocationResult = await testScriptInvocationContext.ResultSource.Task; @@ -300,7 +302,7 @@ public void TestBuildAndGetUri(string pathValue, string expectedUriString) { Port = 8080, }; - DefaultHttpWorkerService defaultHttpWorkerService = new DefaultHttpWorkerService(new HttpClient(), new OptionsWrapper(testOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions)); + DefaultHttpWorkerService defaultHttpWorkerService = new DefaultHttpWorkerService(new HttpClient(), new OptionsWrapper(testOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); Assert.Equal(expectedUriString, defaultHttpWorkerService.BuildAndGetUri(pathValue)); } @@ -308,7 +310,7 @@ public void TestBuildAndGetUri(string pathValue, string expectedUriString) public void AddHeadersTest() { HttpWorkerOptions testOptions = new HttpWorkerOptions(); - DefaultHttpWorkerService defaultHttpWorkerService = new DefaultHttpWorkerService(new HttpClient(), new OptionsWrapper(testOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions)); + DefaultHttpWorkerService defaultHttpWorkerService = new DefaultHttpWorkerService(new HttpClient(), new OptionsWrapper(testOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); HttpRequestMessage input = new HttpRequestMessage(); string invocationId = Guid.NewGuid().ToString(); @@ -332,7 +334,7 @@ public async Task ProcessSimpleHttpTriggerInvocationRequest_Sets_ExpectedResult( }); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions)); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetSimpleHttpTriggerScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger); await _defaultHttpWorkerService.ProcessHttpInAndOutInvocationRequest(testScriptInvocationContext); var invocationResult = await testScriptInvocationContext.ResultSource.Task; @@ -364,7 +366,7 @@ public async Task ProcessDefaultInvocationRequest_JsonResponse_Succeeds() .ReturnsAsync(HttpWorkerTestUtilities.GetHttpResponseMessageWithJsonContent()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions)); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger); await _defaultHttpWorkerService.ProcessDefaultInvocationRequest(testScriptInvocationContext); var invocationResult = await testScriptInvocationContext.ResultSource.Task; @@ -384,7 +386,7 @@ public async Task ProcessDefaultInvocationRequest_OkResponse_InvalidBody_Throws( .ReturnsAsync(HttpWorkerTestUtilities.GetValidHttpResponseMessage_JsonType_InvalidContent()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions)); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger); await _defaultHttpWorkerService.ProcessDefaultInvocationRequest(testScriptInvocationContext); InvalidOperationException recodedEx = await Assert.ThrowsAsync(async () => await testScriptInvocationContext.ResultSource.Task); @@ -404,7 +406,7 @@ public async Task ProcessDefaultInvocationRequest_InvalidMediaType_Throws() .ReturnsAsync(HttpWorkerTestUtilities.GetHttpResponseMessageWithStringContent()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions)); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetScriptInvocationContext(TestFunctionName, _testInvocationId, _testLogger); await _defaultHttpWorkerService.ProcessDefaultInvocationRequest(testScriptInvocationContext); InvalidOperationException recodedEx = await Assert.ThrowsAsync(async () => await testScriptInvocationContext.ResultSource.Task); @@ -426,7 +428,7 @@ public async Task ProcessDefaultInvocationRequest_BadRequestResponse_Throws() }); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions)); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetScriptInvocationContext(TestFunctionName, _testInvocationId, _testLogger); await _defaultHttpWorkerService.ProcessDefaultInvocationRequest(testScriptInvocationContext); await Assert.ThrowsAsync(async () => await testScriptInvocationContext.ResultSource.Task); @@ -445,7 +447,7 @@ public void ProcessOutputLogs_Succeeds(HttpScriptInvocationResult httpScriptInvo .ReturnsAsync(HttpWorkerTestUtilities.GetValidHttpResponseMessage()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions)); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); _defaultHttpWorkerService.ProcessLogsFromHttpResponse(HttpWorkerTestUtilities.GetScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger), httpScriptInvocationResult); var testLogs = _functionLogger.GetLogMessages(); if (httpScriptInvocationResult.Logs != null && httpScriptInvocationResult.Logs.Any()) @@ -472,7 +474,7 @@ public void TestPathValue(string functionName, CustomHandlerType type, bool enab Type = type, EnableForwardingHttpRequest = enableForwardingHttpRequest, }; - DefaultHttpWorkerService defaultHttpWorkerService = new DefaultHttpWorkerService(new HttpClient(), new OptionsWrapper(testOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions)); + DefaultHttpWorkerService defaultHttpWorkerService = new DefaultHttpWorkerService(new HttpClient(), new OptionsWrapper(testOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); string actualValue = defaultHttpWorkerService.GetPathValue(testOptions, functionName, testHttpRequest); Assert.Equal(actualValue, expectedValue); } @@ -489,7 +491,7 @@ public async Task IsWorkerReady_Returns_False() .Throws(new HttpRequestException("Invalid http worker service", new SocketException())); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions)); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); bool workerReady = await _defaultHttpWorkerService.IsWorkerReady(CancellationToken.None); Assert.False(workerReady); @@ -510,7 +512,7 @@ public async Task IsWorkerReady_Returns_True() .ReturnsAsync(HttpWorkerTestUtilities.GetValidHttpResponseMessage()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions)); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); bool workerReady = await _defaultHttpWorkerService.IsWorkerReady(CancellationToken.None); Assert.True(workerReady); From 165f2d25d2429e755a1627120fea6a8a1e4ede6e Mon Sep 17 00:00:00 2001 From: satvu Date: Thu, 24 Apr 2025 16:02:29 -0700 Subject: [PATCH 03/16] working e2e --- .../Workers/Http/DefaultHttpWorkerService.cs | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs b/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs index b4693389ec..d5bf829314 100644 --- a/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs +++ b/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs @@ -82,20 +82,36 @@ public Task InvokeAsync(ScriptInvocationContext scriptInvocationContext) internal async Task ProxyInvocationRequest(ScriptInvocationContext scriptInvocationContext) { - var uriString = BuildAndGetUri(scriptInvocationContext.FunctionMetadata.Name); - var uri = new UriBuilder(uriString).Uri; - try { - var input = scriptInvocationContext.Inputs.First(); - HttpRequest httpRequest = input.Val as HttpRequest; + if (!scriptInvocationContext.TryGetHttpRequest(out HttpRequest httpRequest)) + { + throw new InvalidOperationException($"Cannot proxy the HttpTrigger function {scriptInvocationContext.FunctionMetadata.Name} without an input of type {nameof(HttpRequest)}."); + } + + string uriPathValue = GetPathValue(_httpWorkerOptions, scriptInvocationContext.FunctionMetadata.Name, httpRequest); + var uri = GetUriBuilder(uriPathValue).Uri; + + var httpContext = httpRequest.HttpContext; + AddProxyingHeaders(httpRequest, scriptInvocationContext.ExecutionContext.InvocationId.ToString()); _httpProxyService.StartForwarding(scriptInvocationContext, uri); await _httpProxyService.EnsureSuccessfulForwardingAsync(scriptInvocationContext); - var result = new ScriptInvocationResult(); + var result = new ScriptInvocationResult() + { + Outputs = new Dictionary() + }; + BindingMetadata httpOutputBinding = scriptInvocationContext.FunctionMetadata.OutputBindings.FirstOrDefault(); + if (httpOutputBinding != null) + { + // handle http output binding + result.Outputs.Add(httpOutputBinding.Name, "filler"); + // handle $return + result.Return = "filler"; + } scriptInvocationContext.ResultSource.SetResult(result); } catch (Exception exc) @@ -210,15 +226,13 @@ internal void AddProxyingHeaders(HttpRequest httpRequest, string invocationId) var userAgent = $"{HttpWorkerConstants.UserAgentHeaderValue}/{ScriptHost.Version}"; httpRequest.Headers.Remove("User-Agent"); httpRequest.Headers.Append("User-Agent", userAgent); - - // Add header so that the functions middleware skips the script invocation result handling - httpRequest.Headers.TryAdd(ScriptConstants.HttpProxyingEnabled, bool.TrueString); // placeholder http proxying enabled header for now } internal string GetPathValue(HttpWorkerOptions httpWorkerOptions, string functionName, HttpRequest httpRequest) { string pathValue = functionName; - if (httpWorkerOptions.EnableForwardingHttpRequest && httpWorkerOptions.Type == CustomHandlerType.Http) + if ((httpWorkerOptions.EnableForwardingHttpRequest && httpWorkerOptions.Type == CustomHandlerType.Http) || + httpWorkerOptions.EnableProxyingHttpRequest) { pathValue = httpRequest.GetRequestUri().AbsolutePath; } @@ -347,6 +361,11 @@ internal string BuildAndGetUri(string pathValue = null) return new UriBuilder(WorkerConstants.HttpScheme, WorkerConstants.HostName, _httpWorkerOptions.Port, pathValue).ToString(); } + internal UriBuilder GetUriBuilder(string pathValue) + { + return new UriBuilder(WorkerConstants.HttpScheme, WorkerConstants.HostName, _httpWorkerOptions.Port, pathValue); + } + private async Task SendPingRequestAsync(string requestUri, HttpMethod method = null) { HttpRequestMessage httpRequestMessage = new HttpRequestMessage(); From 7b0a7c5dd4a4a0dcba977fc7c0a7fdba71292538 Mon Sep 17 00:00:00 2001 From: satvu Date: Fri, 25 Apr 2025 15:02:21 -0700 Subject: [PATCH 04/16] cleanup, additional yarp comments --- .../FunctionInvocationMiddleware.cs | 2 -- .../ScriptHostBuilderExtensions.cs | 2 ++ .../Workers/Http/DefaultHttpWorkerService.cs | 33 +++++++------------ 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/WebJobs.Script.WebHost/Middleware/FunctionInvocationMiddleware.cs b/src/WebJobs.Script.WebHost/Middleware/FunctionInvocationMiddleware.cs index 0cd3fb9fda..a5dc7a62e3 100644 --- a/src/WebJobs.Script.WebHost/Middleware/FunctionInvocationMiddleware.cs +++ b/src/WebJobs.Script.WebHost/Middleware/FunctionInvocationMiddleware.cs @@ -4,10 +4,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization.Policy; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; diff --git a/src/WebJobs.Script/ScriptHostBuilderExtensions.cs b/src/WebJobs.Script/ScriptHostBuilderExtensions.cs index 278bf4a78f..b007405480 100644 --- a/src/WebJobs.Script/ScriptHostBuilderExtensions.cs +++ b/src/WebJobs.Script/ScriptHostBuilderExtensions.cs @@ -289,6 +289,8 @@ public static IHostBuilder AddScriptHostCore(this IHostBuilder builder, ScriptAp // Core WebJobs/Script Host services services.AddSingleton(); + services.AddSingleton(); + // HTTP Worker services.AddSingleton(); services.AddSingleton(); diff --git a/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs b/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs index d5bf829314..4bbc101f44 100644 --- a/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs +++ b/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs @@ -25,6 +25,7 @@ public class DefaultHttpWorkerService : IHttpWorkerService private readonly ILogger _logger; private readonly bool _enableRequestTracing; private readonly IHttpProxyService _httpProxyService; + private readonly ScriptInvocationResult _successfulInvocationResult; public DefaultHttpWorkerService(IOptions httpWorkerOptions, ILoggerFactory loggerFactory, IEnvironment environment, IOptions scriptHostOptions, IHttpProxyService httpProxyService) @@ -51,6 +52,11 @@ internal DefaultHttpWorkerService(HttpClient httpClient, IOptions() + }; } private static HttpClient CreateHttpClient(IOptions httpWorkerOptions) @@ -89,10 +95,8 @@ internal async Task ProxyInvocationRequest(ScriptInvocationContext scriptInvocat throw new InvalidOperationException($"Cannot proxy the HttpTrigger function {scriptInvocationContext.FunctionMetadata.Name} without an input of type {nameof(HttpRequest)}."); } - string uriPathValue = GetPathValue(_httpWorkerOptions, scriptInvocationContext.FunctionMetadata.Name, httpRequest); - var uri = GetUriBuilder(uriPathValue).Uri; - - var httpContext = httpRequest.HttpContext; + // YARP only requires the destination prefix. The path and query string are added by the YARP proxy during SendAsync using info from the HttpContext. + var uri = new UriBuilder(WorkerConstants.HttpScheme, WorkerConstants.HostName, _httpWorkerOptions.Port).Uri; AddProxyingHeaders(httpRequest, scriptInvocationContext.ExecutionContext.InvocationId.ToString()); @@ -100,19 +104,9 @@ internal async Task ProxyInvocationRequest(ScriptInvocationContext scriptInvocat await _httpProxyService.EnsureSuccessfulForwardingAsync(scriptInvocationContext); - var result = new ScriptInvocationResult() - { - Outputs = new Dictionary() - }; - BindingMetadata httpOutputBinding = scriptInvocationContext.FunctionMetadata.OutputBindings.FirstOrDefault(); - if (httpOutputBinding != null) - { - // handle http output binding - result.Outputs.Add(httpOutputBinding.Name, "filler"); - // handle $return - result.Return = "filler"; - } - scriptInvocationContext.ResultSource.SetResult(result); + var httpContext = httpRequest.HttpContext; + + scriptInvocationContext.ResultSource.SetResult(_successfulInvocationResult); } catch (Exception exc) { @@ -361,11 +355,6 @@ internal string BuildAndGetUri(string pathValue = null) return new UriBuilder(WorkerConstants.HttpScheme, WorkerConstants.HostName, _httpWorkerOptions.Port, pathValue).ToString(); } - internal UriBuilder GetUriBuilder(string pathValue) - { - return new UriBuilder(WorkerConstants.HttpScheme, WorkerConstants.HostName, _httpWorkerOptions.Port, pathValue); - } - private async Task SendPingRequestAsync(string requestUri, HttpMethod method = null) { HttpRequestMessage httpRequestMessage = new HttpRequestMessage(); From 4133b2ab77a06bb35c1a3f9839662aa04601610f Mon Sep 17 00:00:00 2001 From: satvu Date: Mon, 28 Apr 2025 11:48:24 -0700 Subject: [PATCH 05/16] cleanup and tests --- .../WebJobs.Script.Grpc.csproj | 1 - .../FunctionInvocationMiddleware.cs | 9 - .../Workers/Http/DefaultHttpWorkerService.cs | 15 +- .../DefaultHttpWorkerServiceTests.cs | 170 +++++++++++++++--- 4 files changed, 156 insertions(+), 39 deletions(-) diff --git a/src/WebJobs.Script.Grpc/WebJobs.Script.Grpc.csproj b/src/WebJobs.Script.Grpc/WebJobs.Script.Grpc.csproj index d544f908e5..cb059eea7b 100644 --- a/src/WebJobs.Script.Grpc/WebJobs.Script.Grpc.csproj +++ b/src/WebJobs.Script.Grpc/WebJobs.Script.Grpc.csproj @@ -18,7 +18,6 @@ - diff --git a/src/WebJobs.Script.WebHost/Middleware/FunctionInvocationMiddleware.cs b/src/WebJobs.Script.WebHost/Middleware/FunctionInvocationMiddleware.cs index a5dc7a62e3..ee18bb9504 100644 --- a/src/WebJobs.Script.WebHost/Middleware/FunctionInvocationMiddleware.cs +++ b/src/WebJobs.Script.WebHost/Middleware/FunctionInvocationMiddleware.cs @@ -63,15 +63,6 @@ public async Task Invoke(HttpContext context) } } - // TODO: Come back to this after custom handler proxying settings are set up - if (context.Items.TryGetValue("SomeValue", out var customHandlerProxyingEnabled)) - { - if (customHandlerProxyingEnabled?.ToString() == bool.TrueString) - { - return; - } - } - if (nestedProxiesCount > 0) { // if Proxy, the rest of the pipeline will be processed by Proxies in diff --git a/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs b/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs index 4bbc101f44..a5a3ac2c75 100644 --- a/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs +++ b/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs @@ -26,6 +26,7 @@ public class DefaultHttpWorkerService : IHttpWorkerService private readonly bool _enableRequestTracing; private readonly IHttpProxyService _httpProxyService; private readonly ScriptInvocationResult _successfulInvocationResult; + private readonly Uri _destinationPrefix; public DefaultHttpWorkerService(IOptions httpWorkerOptions, ILoggerFactory loggerFactory, IEnvironment environment, IOptions scriptHostOptions, IHttpProxyService httpProxyService) @@ -57,6 +58,8 @@ internal DefaultHttpWorkerService(HttpClient httpClient, IOptions() }; + + _destinationPrefix = new UriBuilder(WorkerConstants.HttpScheme, WorkerConstants.HostName, _httpWorkerOptions.Port).Uri; } private static HttpClient CreateHttpClient(IOptions httpWorkerOptions) @@ -95,17 +98,12 @@ internal async Task ProxyInvocationRequest(ScriptInvocationContext scriptInvocat throw new InvalidOperationException($"Cannot proxy the HttpTrigger function {scriptInvocationContext.FunctionMetadata.Name} without an input of type {nameof(HttpRequest)}."); } - // YARP only requires the destination prefix. The path and query string are added by the YARP proxy during SendAsync using info from the HttpContext. - var uri = new UriBuilder(WorkerConstants.HttpScheme, WorkerConstants.HostName, _httpWorkerOptions.Port).Uri; - AddProxyingHeaders(httpRequest, scriptInvocationContext.ExecutionContext.InvocationId.ToString()); - _httpProxyService.StartForwarding(scriptInvocationContext, uri); + // YARP only requires the destination prefix. The path and query string are added by the YARP proxy during SendAsync using info from the HttpContext. + _httpProxyService.StartForwarding(scriptInvocationContext, _destinationPrefix); await _httpProxyService.EnsureSuccessfulForwardingAsync(scriptInvocationContext); - - var httpContext = httpRequest.HttpContext; - scriptInvocationContext.ResultSource.SetResult(_successfulInvocationResult); } catch (Exception exc) @@ -225,8 +223,7 @@ internal void AddProxyingHeaders(HttpRequest httpRequest, string invocationId) internal string GetPathValue(HttpWorkerOptions httpWorkerOptions, string functionName, HttpRequest httpRequest) { string pathValue = functionName; - if ((httpWorkerOptions.EnableForwardingHttpRequest && httpWorkerOptions.Type == CustomHandlerType.Http) || - httpWorkerOptions.EnableProxyingHttpRequest) + if (httpWorkerOptions.EnableForwardingHttpRequest && httpWorkerOptions.Type == CustomHandlerType.Http) { pathValue = httpRequest.GetRequestUri().AbsolutePath; } diff --git a/test/WebJobs.Script.Tests/HttpWorker/DefaultHttpWorkerServiceTests.cs b/test/WebJobs.Script.Tests/HttpWorker/DefaultHttpWorkerServiceTests.cs index 001d42612d..605607cb62 100644 --- a/test/WebJobs.Script.Tests/HttpWorker/DefaultHttpWorkerServiceTests.cs +++ b/test/WebJobs.Script.Tests/HttpWorker/DefaultHttpWorkerServiceTests.cs @@ -12,9 +12,11 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.Azure.WebJobs.Script.Description; using Microsoft.Azure.WebJobs.Script.Extensions; using Microsoft.Azure.WebJobs.Script.Workers; using Microsoft.Azure.WebJobs.Script.Workers.Http; +using Microsoft.Extensions.Azure; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; @@ -39,7 +41,7 @@ public class DefaultHttpWorkerServiceTests private int _defaultPort = 8090; private TestLogger _testLogger = new TestLogger("ServiceLogger"); private TestLogger _functionLogger = new TestLogger(TestFunctionName); - private Mock mockHttpProxyService; + private Mock _mockHttpProxyService; public DefaultHttpWorkerServiceTests() { @@ -55,7 +57,7 @@ public DefaultHttpWorkerServiceTests() { FunctionTimeout = TimeSpan.FromMinutes(15) }; - mockHttpProxyService = new Mock(MockBehavior.Strict); + _mockHttpProxyService = new Mock(MockBehavior.Strict); } public static IEnumerable TestLogs @@ -79,7 +81,7 @@ public async Task ProcessDefaultInvocationRequest_Succeeds() .ReturnsAsync(HttpWorkerTestUtilities.GetValidHttpResponseMessage()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), _mockHttpProxyService.Object); Assert.Equal(_httpClient.Timeout, _scriptJobHostOptions.FunctionTimeout.Value.Add(TimeSpan.FromMinutes(1))); var testScriptInvocationContext = HttpWorkerTestUtilities.GetScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger); await _defaultHttpWorkerService.ProcessDefaultInvocationRequest(testScriptInvocationContext); @@ -110,7 +112,7 @@ public async Task ProcessDefaultInvocationRequest_CustomHandler_EnableRequestFor .ReturnsAsync(HttpWorkerTestUtilities.GetValidHttpResponseMessageWithJsonRes()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(customHandlerOptions), _testLogger, _testEnvironment, new OptionsWrapper(scriptJobHostOptionsNoTimeout), mockHttpProxyService.Object); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(customHandlerOptions), _testLogger, _testEnvironment, new OptionsWrapper(scriptJobHostOptionsNoTimeout), _mockHttpProxyService.Object); Assert.Equal(_httpClient.Timeout, TimeSpan.FromMilliseconds(int.MaxValue)); var testScriptInvocationContext = HttpWorkerTestUtilities.GetSimpleHttpTriggerScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger); await _defaultHttpWorkerService.InvokeAsync(testScriptInvocationContext); @@ -142,7 +144,7 @@ public async Task ProcessDefaultInvocationRequest_DataType_Binary_Succeeds() .ReturnsAsync(HttpWorkerTestUtilities.GetValidHttpResponseMessage_DataType_Binary_Data()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), _mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger, WebJobs.Script.Description.DataType.Binary); await _defaultHttpWorkerService.ProcessDefaultInvocationRequest(testScriptInvocationContext); var invocationResult = await testScriptInvocationContext.ResultSource.Task; @@ -178,7 +180,7 @@ public async Task ProcessDefaultInvocationRequest_BinaryData_Succeeds() .ReturnsAsync(HttpWorkerTestUtilities.GetValidHttpResponseMessage_Binary_Data()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), _mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger); await _defaultHttpWorkerService.ProcessDefaultInvocationRequest(testScriptInvocationContext); var invocationResult = await testScriptInvocationContext.ResultSource.Task; @@ -216,7 +218,7 @@ public async Task ProcessPing_Succeeds() .ReturnsAsync(HttpWorkerTestUtilities.GetSimpleNotFoundHttpResponseMessage()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), _mockHttpProxyService.Object); await _defaultHttpWorkerService.PingAsync(); handlerMock.VerifyAll(); } @@ -233,7 +235,7 @@ public async Task ProcessSimpleHttpTriggerInvocationRequest_Succeeds() .ReturnsAsync(HttpWorkerTestUtilities.GetValidSimpleHttpResponseMessage()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), _mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetSimpleHttpTriggerScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger); await _defaultHttpWorkerService.ProcessHttpInAndOutInvocationRequest(testScriptInvocationContext); var invocationResult = await testScriptInvocationContext.ResultSource.Task; @@ -271,7 +273,7 @@ public async Task ProcessSimpleHttpTriggerInvocationRequest_CustomHandler_Enable .ReturnsAsync(HttpWorkerTestUtilities.GetValidSimpleHttpResponseMessage()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(customHandlerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(customHandlerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), _mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetSimpleHttpTriggerScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger); await _defaultHttpWorkerService.InvokeAsync(testScriptInvocationContext); var invocationResult = await testScriptInvocationContext.ResultSource.Task; @@ -302,7 +304,7 @@ public void TestBuildAndGetUri(string pathValue, string expectedUriString) { Port = 8080, }; - DefaultHttpWorkerService defaultHttpWorkerService = new DefaultHttpWorkerService(new HttpClient(), new OptionsWrapper(testOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); + DefaultHttpWorkerService defaultHttpWorkerService = new DefaultHttpWorkerService(new HttpClient(), new OptionsWrapper(testOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), _mockHttpProxyService.Object); Assert.Equal(expectedUriString, defaultHttpWorkerService.BuildAndGetUri(pathValue)); } @@ -310,7 +312,7 @@ public void TestBuildAndGetUri(string pathValue, string expectedUriString) public void AddHeadersTest() { HttpWorkerOptions testOptions = new HttpWorkerOptions(); - DefaultHttpWorkerService defaultHttpWorkerService = new DefaultHttpWorkerService(new HttpClient(), new OptionsWrapper(testOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); + DefaultHttpWorkerService defaultHttpWorkerService = new DefaultHttpWorkerService(new HttpClient(), new OptionsWrapper(testOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), _mockHttpProxyService.Object); HttpRequestMessage input = new HttpRequestMessage(); string invocationId = Guid.NewGuid().ToString(); @@ -334,7 +336,7 @@ public async Task ProcessSimpleHttpTriggerInvocationRequest_Sets_ExpectedResult( }); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), _mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetSimpleHttpTriggerScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger); await _defaultHttpWorkerService.ProcessHttpInAndOutInvocationRequest(testScriptInvocationContext); var invocationResult = await testScriptInvocationContext.ResultSource.Task; @@ -366,7 +368,7 @@ public async Task ProcessDefaultInvocationRequest_JsonResponse_Succeeds() .ReturnsAsync(HttpWorkerTestUtilities.GetHttpResponseMessageWithJsonContent()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), _mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger); await _defaultHttpWorkerService.ProcessDefaultInvocationRequest(testScriptInvocationContext); var invocationResult = await testScriptInvocationContext.ResultSource.Task; @@ -386,7 +388,7 @@ public async Task ProcessDefaultInvocationRequest_OkResponse_InvalidBody_Throws( .ReturnsAsync(HttpWorkerTestUtilities.GetValidHttpResponseMessage_JsonType_InvalidContent()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), _mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger); await _defaultHttpWorkerService.ProcessDefaultInvocationRequest(testScriptInvocationContext); InvalidOperationException recodedEx = await Assert.ThrowsAsync(async () => await testScriptInvocationContext.ResultSource.Task); @@ -406,7 +408,7 @@ public async Task ProcessDefaultInvocationRequest_InvalidMediaType_Throws() .ReturnsAsync(HttpWorkerTestUtilities.GetHttpResponseMessageWithStringContent()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), _mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetScriptInvocationContext(TestFunctionName, _testInvocationId, _testLogger); await _defaultHttpWorkerService.ProcessDefaultInvocationRequest(testScriptInvocationContext); InvalidOperationException recodedEx = await Assert.ThrowsAsync(async () => await testScriptInvocationContext.ResultSource.Task); @@ -428,7 +430,7 @@ public async Task ProcessDefaultInvocationRequest_BadRequestResponse_Throws() }); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), _mockHttpProxyService.Object); var testScriptInvocationContext = HttpWorkerTestUtilities.GetScriptInvocationContext(TestFunctionName, _testInvocationId, _testLogger); await _defaultHttpWorkerService.ProcessDefaultInvocationRequest(testScriptInvocationContext); await Assert.ThrowsAsync(async () => await testScriptInvocationContext.ResultSource.Task); @@ -447,7 +449,7 @@ public void ProcessOutputLogs_Succeeds(HttpScriptInvocationResult httpScriptInvo .ReturnsAsync(HttpWorkerTestUtilities.GetValidHttpResponseMessage()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), _mockHttpProxyService.Object); _defaultHttpWorkerService.ProcessLogsFromHttpResponse(HttpWorkerTestUtilities.GetScriptInvocationContext(TestFunctionName, _testInvocationId, _functionLogger), httpScriptInvocationResult); var testLogs = _functionLogger.GetLogMessages(); if (httpScriptInvocationResult.Logs != null && httpScriptInvocationResult.Logs.Any()) @@ -474,7 +476,7 @@ public void TestPathValue(string functionName, CustomHandlerType type, bool enab Type = type, EnableForwardingHttpRequest = enableForwardingHttpRequest, }; - DefaultHttpWorkerService defaultHttpWorkerService = new DefaultHttpWorkerService(new HttpClient(), new OptionsWrapper(testOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); + DefaultHttpWorkerService defaultHttpWorkerService = new DefaultHttpWorkerService(new HttpClient(), new OptionsWrapper(testOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), _mockHttpProxyService.Object); string actualValue = defaultHttpWorkerService.GetPathValue(testOptions, functionName, testHttpRequest); Assert.Equal(actualValue, expectedValue); } @@ -491,7 +493,7 @@ public async Task IsWorkerReady_Returns_False() .Throws(new HttpRequestException("Invalid http worker service", new SocketException())); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), _mockHttpProxyService.Object); bool workerReady = await _defaultHttpWorkerService.IsWorkerReady(CancellationToken.None); Assert.False(workerReady); @@ -512,12 +514,128 @@ public async Task IsWorkerReady_Returns_True() .ReturnsAsync(HttpWorkerTestUtilities.GetValidHttpResponseMessage()); _httpClient = new HttpClient(handlerMock.Object); - _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), mockHttpProxyService.Object); + _defaultHttpWorkerService = new DefaultHttpWorkerService(_httpClient, new OptionsWrapper(_httpWorkerOptions), _testLogger, _testEnvironment, new OptionsWrapper(_scriptJobHostOptions), _mockHttpProxyService.Object); bool workerReady = await _defaultHttpWorkerService.IsWorkerReady(CancellationToken.None); Assert.True(workerReady); } + [Fact] + public async Task ProxyInvocationRequest_Success() + { + var testUri = new Uri("http://localhost:7071/api/test"); + var testInvocationId = Guid.NewGuid(); + var testFunctionName = "TestFunction"; + + // When there is a simple HttpTrigger and EnableHttpProxyingRequest is set to true proxy, proxying service should be called and invocation result set to true upon completion + var testScriptInvocationContext = HttpWorkerTestUtilities.GetSimpleHttpTriggerScriptInvocationContext(testFunctionName, testInvocationId, _functionLogger); + var mockHttpClient = new Mock(); + HttpWorkerOptions testOptions = new HttpWorkerOptions + { + EnableProxyingHttpRequest = true + }; + + var mockHttpRequest = SetUpMockHttpRequestForProxying(); + + // Add the mocked HttpRequest to the ScriptInvocationContext + testScriptInvocationContext.Inputs = new List<(string Name, DataType Type, object Val)> + { + ("req", DataType.Undefined, mockHttpRequest.Object) + }; + + testScriptInvocationContext.Inputs = new List<(string Name, DataType Type, object Val)> { ("req", DataType.Undefined, mockHttpRequest.Object) }; + + _mockHttpProxyService + .Setup(m => m.StartForwarding(testScriptInvocationContext, It.IsAny())) + .Verifiable(); + + _mockHttpProxyService + .Setup(m => m.EnsureSuccessfulForwardingAsync(testScriptInvocationContext)) + .Returns(Task.CompletedTask) + .Verifiable(); + + _defaultHttpWorkerService = new DefaultHttpWorkerService( + mockHttpClient.Object, + new OptionsWrapper(testOptions), + _testLogger, + _testEnvironment, + new OptionsWrapper(_scriptJobHostOptions), + _mockHttpProxyService.Object); + + await _defaultHttpWorkerService.InvokeAsync(testScriptInvocationContext); + + _mockHttpProxyService.Verify(m => m.StartForwarding(testScriptInvocationContext, It.IsAny()), Times.Once); + _mockHttpProxyService.Verify(m => m.EnsureSuccessfulForwardingAsync(testScriptInvocationContext), Times.Once); + Assert.True(testScriptInvocationContext.ResultSource.Task.IsCompletedSuccessfully); + } + + [Fact] + public async Task ProxyInvocationRequest_ThrowsException_WhenHttpRequestIsMissing() + { + var mockHttpClient = new Mock(); + var testInvocationId = Guid.NewGuid(); + var testFunctionName = "TestFunction"; + var testScriptInvocationContext = HttpWorkerTestUtilities.GetSimpleHttpTriggerScriptInvocationContext(testFunctionName, testInvocationId, _functionLogger); + + _defaultHttpWorkerService = new DefaultHttpWorkerService( + mockHttpClient.Object, + new OptionsWrapper(_httpWorkerOptions), + _testLogger, + _testEnvironment, + new OptionsWrapper(_scriptJobHostOptions), + _mockHttpProxyService.Object); + + await _defaultHttpWorkerService.ProxyInvocationRequest(testScriptInvocationContext); + + Assert.True(testScriptInvocationContext.ResultSource.Task.IsFaulted); + Assert.IsType(testScriptInvocationContext.ResultSource.Task.Exception.InnerException); + Assert.Equal("Cannot proxy the HttpTrigger function TestFunction without an input of type HttpRequest.", testScriptInvocationContext.ResultSource.Task.Exception.InnerException.Message); + } + + [Fact] + public async Task ProxyInvocationRequest_HandlesForwardingError() + { + var mockHttpClient = new Mock(); + var testUri = new Uri("http://localhost:7071/api/test"); + var testInvocationId = Guid.NewGuid(); + var testFunctionName = "TestFunction"; + var testScriptInvocationContext = HttpWorkerTestUtilities.GetSimpleHttpTriggerScriptInvocationContext(testFunctionName, testInvocationId, _functionLogger); + + var mockHttpRequest = SetUpMockHttpRequestForProxying(); + + // Add the mocked HttpRequest to the ScriptInvocationContext + testScriptInvocationContext.Inputs = new List<(string Name, DataType Type, object Val)> + { + ("req", DataType.Undefined, mockHttpRequest.Object) + }; + + testScriptInvocationContext.Inputs = new List<(string Name, DataType Type, object Val)> { ("req", DataType.Undefined, mockHttpRequest.Object) }; + + _mockHttpProxyService + .Setup(m => m.StartForwarding(testScriptInvocationContext, It.IsAny())) + .Verifiable(); + + _mockHttpProxyService + .Setup(m => m.EnsureSuccessfulForwardingAsync(testScriptInvocationContext)) + .ThrowsAsync(new HttpForwardingException("Forwarding failed")) + .Verifiable(); + + _defaultHttpWorkerService = new DefaultHttpWorkerService( + mockHttpClient.Object, + new OptionsWrapper(_httpWorkerOptions), + _testLogger, + _testEnvironment, + new OptionsWrapper(_scriptJobHostOptions), + _mockHttpProxyService.Object); + + await _defaultHttpWorkerService.ProxyInvocationRequest(testScriptInvocationContext); + + _mockHttpProxyService.Verify(m => m.StartForwarding(testScriptInvocationContext, It.IsAny()), Times.Once); + _mockHttpProxyService.Verify(m => m.EnsureSuccessfulForwardingAsync(testScriptInvocationContext), Times.Once); + Assert.True(testScriptInvocationContext.ResultSource.Task.IsFaulted); + Assert.IsType(testScriptInvocationContext.ResultSource.Task.Exception.InnerException); + } + private async void ValidateDefaultInvocationRequest(HttpRequestMessage httpRequestMessage) { Assert.Contains($"{HttpWorkerConstants.UserAgentHeaderValue}/{ScriptHost.Version}", httpRequestMessage.Headers.UserAgent.ToString()); @@ -596,5 +714,17 @@ private void RequestHandler(HttpRequestMessage httpRequestMessage) { //used for tests that do not need request validation } + + private Mock SetUpMockHttpRequestForProxying() + { + var mockHttpRequest = new Mock(); + var mockHttpContext = new Mock(); + var headers = new HeaderDictionary(); + mockHttpRequest.SetupGet(r => r.Headers).Returns(headers); + mockHttpRequest.SetupGet(r => r.HttpContext).Returns(mockHttpContext.Object); + mockHttpContext.Setup(mockHttpContext => mockHttpContext.Items.ContainsKey(ScriptConstants.AzureFunctionsHttpTriggerContext)).Returns(true); + + return mockHttpRequest; + } } } From ef032c5aab3c8c8aecf40a6743fd57d13dc2e9b1 Mon Sep 17 00:00:00 2001 From: satvu Date: Mon, 28 Apr 2025 11:50:43 -0700 Subject: [PATCH 06/16] spacing fix --- src/WebJobs.Script/WebJobs.Script.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WebJobs.Script/WebJobs.Script.csproj b/src/WebJobs.Script/WebJobs.Script.csproj index a13559ae82..2117fb7ad7 100644 --- a/src/WebJobs.Script/WebJobs.Script.csproj +++ b/src/WebJobs.Script/WebJobs.Script.csproj @@ -73,7 +73,7 @@ - + From 436c6edb884f60456226706ea9464d07cff6de66 Mon Sep 17 00:00:00 2001 From: satvu Date: Mon, 28 Apr 2025 11:53:50 -0700 Subject: [PATCH 07/16] cleanup summary --- .../Workers/Http/Configuration/HttpWorkerOptions.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptions.cs b/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptions.cs index cdf9762ab1..09f0354b5c 100644 --- a/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptions.cs +++ b/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptions.cs @@ -15,10 +15,14 @@ public class HttpWorkerOptions public int Port { get; set; } - // This is the legacy setting that rebuilds the HTTP request for forwrding + /// + /// Setting that enables the host to rebuild the initial invocation HTTP Request and send the copy to the worker process. + /// public bool EnableForwardingHttpRequest { get; set; } - // This uses YARP to forward the HTTP request + /// + /// Setting that enables YARP to proxy the invocation HTTP request to the worker process. + /// public bool EnableProxyingHttpRequest { get; set; } public TimeSpan InitializationTimeout { get; set; } = TimeSpan.FromSeconds(30); From 944e1f5b2c71471e5e60ee1e4cb23684b9e465a4 Mon Sep 17 00:00:00 2001 From: satvu Date: Tue, 29 Apr 2025 15:01:58 -0700 Subject: [PATCH 08/16] resolve comments --- .../Workers/Http/Configuration/HttpWorkerOptions.cs | 4 ++-- .../Http/Configuration/HttpWorkerOptionsSetup.cs | 2 +- .../Workers/Http/DefaultHttpWorkerService.cs | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptions.cs b/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptions.cs index 09f0354b5c..df3de0bd3a 100644 --- a/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptions.cs +++ b/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptions.cs @@ -16,12 +16,12 @@ public class HttpWorkerOptions public int Port { get; set; } /// - /// Setting that enables the host to rebuild the initial invocation HTTP Request and send the copy to the worker process. + /// Gets or sets a value indicating whether the host will rebuild the initial invocation HTTP Request and send the copy to the worker process. /// public bool EnableForwardingHttpRequest { get; set; } /// - /// Setting that enables YARP to proxy the invocation HTTP request to the worker process. + /// Gets or sets a value indicating whether the host will proxy the invocation HTTP request to the worker process. /// public bool EnableProxyingHttpRequest { get; set; } diff --git a/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptionsSetup.cs b/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptionsSetup.cs index ad9b37a0a9..78c3b62ab5 100644 --- a/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptionsSetup.cs +++ b/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptionsSetup.cs @@ -46,7 +46,7 @@ public void Configure(HttpWorkerOptions options) ConfigureWorkerDescription(options, customHandlerSection); if (options.Type == CustomHandlerType.None) { - // CustomHandlerType.None is only for maintaining backward compatibilty with httpWorker section. + // CustomHandlerType.None is only for maintaining backward compatability with httpWorker section. _logger.LogWarning($"CustomHandlerType {CustomHandlerType.None} is not supported. Defaulting to {CustomHandlerType.Http}."); options.Type = CustomHandlerType.Http; } diff --git a/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs b/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs index a5a3ac2c75..98780cd265 100644 --- a/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs +++ b/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs @@ -27,6 +27,7 @@ public class DefaultHttpWorkerService : IHttpWorkerService private readonly IHttpProxyService _httpProxyService; private readonly ScriptInvocationResult _successfulInvocationResult; private readonly Uri _destinationPrefix; + private readonly string _userAgentString; public DefaultHttpWorkerService(IOptions httpWorkerOptions, ILoggerFactory loggerFactory, IEnvironment environment, IOptions scriptHostOptions, IHttpProxyService httpProxyService) @@ -60,6 +61,7 @@ internal DefaultHttpWorkerService(HttpClient httpClient, IOptions httpWorkerOptions) @@ -203,10 +205,10 @@ internal void AddHeaders(HttpRequestMessage httpRequest, string invocationId) { httpRequest.Headers.Add(HttpWorkerConstants.HostVersionHeaderName, ScriptHost.Version); httpRequest.Headers.Add(HttpWorkerConstants.InvocationIdHeaderName, invocationId); - httpRequest.Headers.UserAgent.ParseAdd($"{HttpWorkerConstants.UserAgentHeaderValue}/{ScriptHost.Version}"); + httpRequest.Headers.UserAgent.ParseAdd(_userAgentString); } - internal void AddProxyingHeaders(HttpRequest httpRequest, string invocationId) + private void AddProxyingHeaders(HttpRequest httpRequest, string invocationId) { httpRequest.Headers.TryAdd(HttpWorkerConstants.HostVersionHeaderName, ScriptHost.Version); @@ -215,9 +217,7 @@ internal void AddProxyingHeaders(HttpRequest httpRequest, string invocationId) httpRequest.Headers[HttpWorkerConstants.InvocationIdHeaderName] = invocationId; } - var userAgent = $"{HttpWorkerConstants.UserAgentHeaderValue}/{ScriptHost.Version}"; - httpRequest.Headers.Remove("User-Agent"); - httpRequest.Headers.Append("User-Agent", userAgent); + httpRequest.Headers.UserAgent = _userAgentString; } internal string GetPathValue(HttpWorkerOptions httpWorkerOptions, string functionName, HttpRequest httpRequest) From 2256ee4e4b0a73b0c959eb018f39c55f6d26134f Mon Sep 17 00:00:00 2001 From: satvu Date: Fri, 9 May 2025 11:39:42 -0700 Subject: [PATCH 09/16] update namespace --- src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs | 1 + .../HttpForwardingException.cs | 2 +- src/WebJobs.Script/HttpProxyService/DefaultHttpProxyService.cs | 3 ++- src/WebJobs.Script/HttpProxyService/IHttpProxyService.cs | 2 +- src/WebJobs.Script/HttpProxyService/RetryProxyHandler.cs | 2 +- src/WebJobs.Script/ScriptHostBuilderExtensions.cs | 1 + src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs | 1 + .../HttpWorker/DefaultHttpWorkerServiceTests.cs | 2 ++ 8 files changed, 10 insertions(+), 4 deletions(-) rename src/WebJobs.Script/{HttpProxyService => Exceptions}/HttpForwardingException.cs (90%) diff --git a/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs b/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs index 88fcda9b91..789322d3f6 100644 --- a/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs +++ b/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs @@ -26,6 +26,7 @@ using Microsoft.Azure.WebJobs.Script.Grpc.Eventing; using Microsoft.Azure.WebJobs.Script.Grpc.Extensions; using Microsoft.Azure.WebJobs.Script.Grpc.Messages; +using Microsoft.Azure.WebJobs.Script.HttpProxyService; using Microsoft.Azure.WebJobs.Script.ManagedDependencies; using Microsoft.Azure.WebJobs.Script.Workers; using Microsoft.Azure.WebJobs.Script.Workers.Rpc; diff --git a/src/WebJobs.Script/HttpProxyService/HttpForwardingException.cs b/src/WebJobs.Script/Exceptions/HttpForwardingException.cs similarity index 90% rename from src/WebJobs.Script/HttpProxyService/HttpForwardingException.cs rename to src/WebJobs.Script/Exceptions/HttpForwardingException.cs index 3dbba3bb64..d00a25c2a7 100644 --- a/src/WebJobs.Script/HttpProxyService/HttpForwardingException.cs +++ b/src/WebJobs.Script/Exceptions/HttpForwardingException.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Azure.WebJobs.Script +namespace Microsoft.Azure.WebJobs.Script.Exceptions { internal class HttpForwardingException : Exception { diff --git a/src/WebJobs.Script/HttpProxyService/DefaultHttpProxyService.cs b/src/WebJobs.Script/HttpProxyService/DefaultHttpProxyService.cs index 65a6db1cfd..c7f2b7b9e9 100644 --- a/src/WebJobs.Script/HttpProxyService/DefaultHttpProxyService.cs +++ b/src/WebJobs.Script/HttpProxyService/DefaultHttpProxyService.cs @@ -7,11 +7,12 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Azure.WebJobs.Script.Description; +using Microsoft.Azure.WebJobs.Script.Exceptions; using Microsoft.Azure.WebJobs.Script.Workers; using Microsoft.Extensions.Logging; using Yarp.ReverseProxy.Forwarder; -namespace Microsoft.Azure.WebJobs.Script +namespace Microsoft.Azure.WebJobs.Script.HttpProxyService { internal class DefaultHttpProxyService : IHttpProxyService, IDisposable { diff --git a/src/WebJobs.Script/HttpProxyService/IHttpProxyService.cs b/src/WebJobs.Script/HttpProxyService/IHttpProxyService.cs index 6c820ed86d..6f17d3f8e9 100644 --- a/src/WebJobs.Script/HttpProxyService/IHttpProxyService.cs +++ b/src/WebJobs.Script/HttpProxyService/IHttpProxyService.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Microsoft.Azure.WebJobs.Script.Description; -namespace Microsoft.Azure.WebJobs.Script +namespace Microsoft.Azure.WebJobs.Script.HttpProxyService { public interface IHttpProxyService { diff --git a/src/WebJobs.Script/HttpProxyService/RetryProxyHandler.cs b/src/WebJobs.Script/HttpProxyService/RetryProxyHandler.cs index 011fe39bb9..3bbed3ae4c 100644 --- a/src/WebJobs.Script/HttpProxyService/RetryProxyHandler.cs +++ b/src/WebJobs.Script/HttpProxyService/RetryProxyHandler.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; -namespace Microsoft.Azure.WebJobs.Script +namespace Microsoft.Azure.WebJobs.Script.HttpProxyService { internal sealed class RetryProxyHandler : DelegatingHandler { diff --git a/src/WebJobs.Script/ScriptHostBuilderExtensions.cs b/src/WebJobs.Script/ScriptHostBuilderExtensions.cs index b007405480..f4234afa78 100644 --- a/src/WebJobs.Script/ScriptHostBuilderExtensions.cs +++ b/src/WebJobs.Script/ScriptHostBuilderExtensions.cs @@ -32,6 +32,7 @@ using Microsoft.Azure.WebJobs.Script.ExtensionBundle; using Microsoft.Azure.WebJobs.Script.FileProvisioning; using Microsoft.Azure.WebJobs.Script.Http; +using Microsoft.Azure.WebJobs.Script.HttpProxyService; using Microsoft.Azure.WebJobs.Script.ManagedDependencies; using Microsoft.Azure.WebJobs.Script.Scale; using Microsoft.Azure.WebJobs.Script.Workers; diff --git a/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs b/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs index 98780cd265..48c991f667 100644 --- a/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs +++ b/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs @@ -13,6 +13,7 @@ using Microsoft.Azure.WebJobs.Script.Description; using Microsoft.Azure.WebJobs.Script.Diagnostics.Extensions; using Microsoft.Azure.WebJobs.Script.Extensions; +using Microsoft.Azure.WebJobs.Script.HttpProxyService; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; diff --git a/test/WebJobs.Script.Tests/HttpWorker/DefaultHttpWorkerServiceTests.cs b/test/WebJobs.Script.Tests/HttpWorker/DefaultHttpWorkerServiceTests.cs index 605607cb62..0d06160f47 100644 --- a/test/WebJobs.Script.Tests/HttpWorker/DefaultHttpWorkerServiceTests.cs +++ b/test/WebJobs.Script.Tests/HttpWorker/DefaultHttpWorkerServiceTests.cs @@ -13,7 +13,9 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Azure.WebJobs.Script.Description; +using Microsoft.Azure.WebJobs.Script.Exceptions; using Microsoft.Azure.WebJobs.Script.Extensions; +using Microsoft.Azure.WebJobs.Script.HttpProxyService; using Microsoft.Azure.WebJobs.Script.Workers; using Microsoft.Azure.WebJobs.Script.Workers.Http; using Microsoft.Extensions.Azure; From a05018b36a2999aa9bc0db53ce0dd7184d3c5f51 Mon Sep 17 00:00:00 2001 From: satvu Date: Fri, 9 May 2025 11:59:33 -0700 Subject: [PATCH 10/16] fix missing refs --- src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannelFactory.cs | 1 + src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs | 1 + .../ApplicationInsights/ApplicationInsightsTestFixture.cs | 1 + test/WebJobs.Script.Tests/Workers/RetryProxyHandlerTests.cs | 5 +---- .../Workers/Rpc/GrpcWorkerChannelTests.cs | 1 + 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannelFactory.cs b/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannelFactory.cs index 5bdb4386e2..b808325b72 100644 --- a/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannelFactory.cs +++ b/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannelFactory.cs @@ -8,6 +8,7 @@ using Microsoft.Azure.WebJobs.Script.Diagnostics; using Microsoft.Azure.WebJobs.Script.Eventing; using Microsoft.Azure.WebJobs.Script.Grpc.Eventing; +using Microsoft.Azure.WebJobs.Script.HttpProxyService; using Microsoft.Azure.WebJobs.Script.Workers; using Microsoft.Azure.WebJobs.Script.Workers.Rpc; using Microsoft.Azure.WebJobs.Script.Workers.SharedMemoryDataTransfer; diff --git a/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs b/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs index 19a8578263..3e148d0513 100644 --- a/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs +++ b/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using Microsoft.Azure.WebJobs.Script.Grpc.Messages; +using Microsoft.Azure.WebJobs.Script.HttpProxyService; using Microsoft.Azure.WebJobs.Script.Workers.Rpc; using Microsoft.Extensions.DependencyInjection; diff --git a/test/WebJobs.Script.Tests.Integration/ApplicationInsights/ApplicationInsightsTestFixture.cs b/test/WebJobs.Script.Tests.Integration/ApplicationInsights/ApplicationInsightsTestFixture.cs index a927b6a82b..42dde6d50b 100644 --- a/test/WebJobs.Script.Tests.Integration/ApplicationInsights/ApplicationInsightsTestFixture.cs +++ b/test/WebJobs.Script.Tests.Integration/ApplicationInsights/ApplicationInsightsTestFixture.cs @@ -11,6 +11,7 @@ using Microsoft.Azure.WebJobs.Script.Diagnostics; using Microsoft.Azure.WebJobs.Script.Eventing; using Microsoft.Azure.WebJobs.Script.Grpc; +using Microsoft.Azure.WebJobs.Script.HttpProxyService; using Microsoft.Azure.WebJobs.Script.Workers; using Microsoft.Azure.WebJobs.Script.Workers.Rpc; using Microsoft.Azure.WebJobs.Script.Workers.SharedMemoryDataTransfer; diff --git a/test/WebJobs.Script.Tests/Workers/RetryProxyHandlerTests.cs b/test/WebJobs.Script.Tests/Workers/RetryProxyHandlerTests.cs index 3c42f2eae6..31f8a9c412 100644 --- a/test/WebJobs.Script.Tests/Workers/RetryProxyHandlerTests.cs +++ b/test/WebJobs.Script.Tests/Workers/RetryProxyHandlerTests.cs @@ -1,14 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; using System.Net.Http; using System.Reflection; using System.Threading; using System.Threading.Tasks; -using System.Web.Http; -using Microsoft.Azure.WebJobs.Script.Grpc; +using Microsoft.Azure.WebJobs.Script.HttpProxyService; using Microsoft.Extensions.Logging.Abstractions; using Xunit; diff --git a/test/WebJobs.Script.Tests/Workers/Rpc/GrpcWorkerChannelTests.cs b/test/WebJobs.Script.Tests/Workers/Rpc/GrpcWorkerChannelTests.cs index 6489f6c756..578feb65ea 100644 --- a/test/WebJobs.Script.Tests/Workers/Rpc/GrpcWorkerChannelTests.cs +++ b/test/WebJobs.Script.Tests/Workers/Rpc/GrpcWorkerChannelTests.cs @@ -17,6 +17,7 @@ using Microsoft.Azure.WebJobs.Script.Grpc; using Microsoft.Azure.WebJobs.Script.Grpc.Eventing; using Microsoft.Azure.WebJobs.Script.Grpc.Messages; +using Microsoft.Azure.WebJobs.Script.HttpProxyService; using Microsoft.Azure.WebJobs.Script.Workers; using Microsoft.Azure.WebJobs.Script.Workers.FunctionDataCache; using Microsoft.Azure.WebJobs.Script.Workers.Rpc; From 73b561c9fb1c51fb285a09aa76606a7b98e6dcf0 Mon Sep 17 00:00:00 2001 From: satvu Date: Wed, 14 May 2025 10:40:15 -0700 Subject: [PATCH 11/16] cleanup --- .../HttpProxyService/DefaultHttpProxyServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/WebJobs.Script.Tests/HttpProxyService/DefaultHttpProxyServiceTests.cs b/test/WebJobs.Script.Tests/HttpProxyService/DefaultHttpProxyServiceTests.cs index 3356ed2017..6dfa7b1aba 100644 --- a/test/WebJobs.Script.Tests/HttpProxyService/DefaultHttpProxyServiceTests.cs +++ b/test/WebJobs.Script.Tests/HttpProxyService/DefaultHttpProxyServiceTests.cs @@ -6,7 +6,7 @@ using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.Azure.WebJobs.Script.Description; -using Microsoft.Azure.WebJobs.Script.Grpc; +using Microsoft.Azure.WebJobs.Script.HttpProxyService; using Microsoft.Extensions.Logging; using Moq; using Xunit; From d0ad9a94c37183faeaf0b3a3a6d74c808ef36165 Mon Sep 17 00:00:00 2001 From: satvu Date: Thu, 15 May 2025 14:45:37 -0700 Subject: [PATCH 12/16] cleanup add proxying headers --- .../Workers/Http/DefaultHttpWorkerService.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs b/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs index 48c991f667..c83d54196a 100644 --- a/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs +++ b/src/WebJobs.Script/Workers/Http/DefaultHttpWorkerService.cs @@ -211,13 +211,9 @@ internal void AddHeaders(HttpRequestMessage httpRequest, string invocationId) private void AddProxyingHeaders(HttpRequest httpRequest, string invocationId) { - httpRequest.Headers.TryAdd(HttpWorkerConstants.HostVersionHeaderName, ScriptHost.Version); - - if (!httpRequest.Headers.TryAdd(HttpWorkerConstants.InvocationIdHeaderName, invocationId)) - { - httpRequest.Headers[HttpWorkerConstants.InvocationIdHeaderName] = invocationId; - } - + // if there are existing headers, override them + httpRequest.Headers[HttpWorkerConstants.HostVersionHeaderName] = ScriptHost.Version; + httpRequest.Headers[HttpWorkerConstants.InvocationIdHeaderName] = invocationId; httpRequest.Headers.UserAgent = _userAgentString; } From 82f122639b299989c2c7a24428e67e91ca2fe449 Mon Sep 17 00:00:00 2001 From: satvu Date: Mon, 19 May 2025 13:44:51 -0700 Subject: [PATCH 13/16] cleanup service registration --- src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs | 3 --- src/WebJobs.Script/ScriptHostBuilderExtensions.cs | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs b/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs index 3e148d0513..f7f80ae210 100644 --- a/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs +++ b/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs @@ -17,9 +17,6 @@ public static IServiceCollection AddScriptGrpc(this IServiceCollection services) services.AddSingleton(); - services.AddHttpForwarder(); - services.AddSingleton(); - return services; } } diff --git a/src/WebJobs.Script/ScriptHostBuilderExtensions.cs b/src/WebJobs.Script/ScriptHostBuilderExtensions.cs index f4234afa78..372c5954d2 100644 --- a/src/WebJobs.Script/ScriptHostBuilderExtensions.cs +++ b/src/WebJobs.Script/ScriptHostBuilderExtensions.cs @@ -290,6 +290,9 @@ public static IHostBuilder AddScriptHostCore(this IHostBuilder builder, ScriptAp // Core WebJobs/Script Host services services.AddSingleton(); + // Add http proxying services - this is used with http streaming workers and custom handlers when enabled + // http streaming capabilities are known following worker initialization so that info isn't available at this stage + services.AddHttpForwarder(); services.AddSingleton(); // HTTP Worker From 8bb0e1bc77bd5ae4089f0f9fb2122bba46ab77bf Mon Sep 17 00:00:00 2001 From: satvu Date: Mon, 19 May 2025 13:45:31 -0700 Subject: [PATCH 14/16] using cleanup --- src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs b/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs index f7f80ae210..5294c4e810 100644 --- a/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs +++ b/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using Microsoft.Azure.WebJobs.Script.Grpc.Messages; -using Microsoft.Azure.WebJobs.Script.HttpProxyService; using Microsoft.Azure.WebJobs.Script.Workers.Rpc; using Microsoft.Extensions.DependencyInjection; From e4e5a9a8c8f54996a41e5f67664555ac74e642f5 Mon Sep 17 00:00:00 2001 From: satvu Date: Tue, 20 May 2025 15:54:22 -0700 Subject: [PATCH 15/16] revert grpc-registration changes --- src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs b/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs index 5294c4e810..19a8578263 100644 --- a/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs +++ b/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs @@ -16,6 +16,9 @@ public static IServiceCollection AddScriptGrpc(this IServiceCollection services) services.AddSingleton(); + services.AddHttpForwarder(); + services.AddSingleton(); + return services; } } From 52ffeffafcfbea0415ac42211326a3a56a98d518 Mon Sep 17 00:00:00 2001 From: satvu Date: Wed, 21 May 2025 11:56:28 -0700 Subject: [PATCH 16/16] update import --- src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs b/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs index 19a8578263..3e148d0513 100644 --- a/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs +++ b/src/WebJobs.Script.Grpc/GrpcServiceCollectionsExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using Microsoft.Azure.WebJobs.Script.Grpc.Messages; +using Microsoft.Azure.WebJobs.Script.HttpProxyService; using Microsoft.Azure.WebJobs.Script.Workers.Rpc; using Microsoft.Extensions.DependencyInjection;