diff --git a/release_notes.md b/release_notes.md
index 6048dbc510..882bd8c6ec 100644
--- a/release_notes.md
+++ b/release_notes.md
@@ -17,4 +17,5 @@
- Improvements to coldstart pipeline (#11102).
- Update Python Worker Version to [4.38.0](https://github.com/Azure/azure-functions-python-worker/releases/tag/4.38.0)
- Only start the Diagnostic Events flush logs timer when events are present, preventing unnecessary flush attempts (#11100).
+- Enable HTTP proxying for custom handlers (#11035)
- Switched memory usage reporting to use CGroup metrics by default for Linux consumption (#11114)
diff --git a/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs b/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs
index 88fcda9b91..7b897bfea0 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.Http;
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.Grpc/Channel/GrpcWorkerChannelFactory.cs b/src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannelFactory.cs
index 5bdb4386e2..1c0113967a 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.Http;
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..d79f651e44 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.Http;
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
using Microsoft.Extensions.DependencyInjection;
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 2d4ff1b23f..ee18bb9504 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;
@@ -57,9 +55,9 @@ 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;
}
diff --git a/src/WebJobs.Script/Description/Workers/ScriptInvocationResult.cs b/src/WebJobs.Script/Description/Workers/ScriptInvocationResult.cs
index 997e903fc2..3a47282118 100644
--- a/src/WebJobs.Script/Description/Workers/ScriptInvocationResult.cs
+++ b/src/WebJobs.Script/Description/Workers/ScriptInvocationResult.cs
@@ -2,11 +2,17 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Collections.Generic;
+using System.Collections.Immutable;
namespace Microsoft.Azure.WebJobs.Script.Description
{
public class ScriptInvocationResult
{
+ public static readonly ScriptInvocationResult Success = new ScriptInvocationResult
+ {
+ Outputs = ImmutableDictionary.Empty
+ };
+
public object Return { get; set; }
public IDictionary Outputs { get; set; }
diff --git a/src/WebJobs.Script.Grpc/Exceptions/HttpForwardingException.cs b/src/WebJobs.Script/Exceptions/HttpForwardingException.cs
similarity index 90%
rename from src/WebJobs.Script.Grpc/Exceptions/HttpForwardingException.cs
rename to src/WebJobs.Script/Exceptions/HttpForwardingException.cs
index b60daba49d..d00a25c2a7 100644
--- a/src/WebJobs.Script.Grpc/Exceptions/HttpForwardingException.cs
+++ b/src/WebJobs.Script/Exceptions/HttpForwardingException.cs
@@ -3,7 +3,7 @@
using System;
-namespace Microsoft.Azure.WebJobs.Script.Grpc.Exceptions
+namespace Microsoft.Azure.WebJobs.Script.Exceptions
{
internal class HttpForwardingException : Exception
{
diff --git a/src/WebJobs.Script.Grpc/Server/DefaultHttpProxyService.cs b/src/WebJobs.Script/Http/DefaultHttpProxyService.cs
similarity index 97%
rename from src/WebJobs.Script.Grpc/Server/DefaultHttpProxyService.cs
rename to src/WebJobs.Script/Http/DefaultHttpProxyService.cs
index 1d164c2c04..d7e6828024 100644
--- a/src/WebJobs.Script.Grpc/Server/DefaultHttpProxyService.cs
+++ b/src/WebJobs.Script/Http/DefaultHttpProxyService.cs
@@ -7,12 +7,12 @@
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.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.Http
{
internal class DefaultHttpProxyService : IHttpProxyService, IDisposable
{
diff --git a/src/WebJobs.Script/Http/DefaultHttpRouteManager.cs b/src/WebJobs.Script/Http/DefaultHttpRouteManager.cs
index 7dc8a145b5..2f87b0af86 100644
--- a/src/WebJobs.Script/Http/DefaultHttpRouteManager.cs
+++ b/src/WebJobs.Script/Http/DefaultHttpRouteManager.cs
@@ -1,9 +1,6 @@
// 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 Microsoft.Azure.WebJobs.Script.WebHost;
-
namespace Microsoft.Azure.WebJobs.Script.Http
{
internal class DefaultHttpRouteManager : IHttpRoutesManager
diff --git a/src/WebJobs.Script.Grpc/Server/IHttpProxyService.cs b/src/WebJobs.Script/Http/IHttpProxyService.cs
similarity index 94%
rename from src/WebJobs.Script.Grpc/Server/IHttpProxyService.cs
rename to src/WebJobs.Script/Http/IHttpProxyService.cs
index 7db2eeaf12..e5cbaae763 100644
--- a/src/WebJobs.Script.Grpc/Server/IHttpProxyService.cs
+++ b/src/WebJobs.Script/Http/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.Http
{
public interface IHttpProxyService
{
diff --git a/src/WebJobs.Script/Http/IHttpRoutesManager.cs b/src/WebJobs.Script/Http/IHttpRoutesManager.cs
index 3df8ae284c..a973d117fd 100644
--- a/src/WebJobs.Script/Http/IHttpRoutesManager.cs
+++ b/src/WebJobs.Script/Http/IHttpRoutesManager.cs
@@ -1,8 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
-using System;
-
namespace Microsoft.Azure.WebJobs.Script
{
public interface IHttpRoutesManager
diff --git a/src/WebJobs.Script.Grpc/Server/RetryProxyHandler.cs b/src/WebJobs.Script/Http/RetryProxyHandler.cs
similarity index 98%
rename from src/WebJobs.Script.Grpc/Server/RetryProxyHandler.cs
rename to src/WebJobs.Script/Http/RetryProxyHandler.cs
index 8659a0403b..8c21e321f1 100644
--- a/src/WebJobs.Script.Grpc/Server/RetryProxyHandler.cs
+++ b/src/WebJobs.Script/Http/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.Http
{
internal sealed class RetryProxyHandler : DelegatingHandler
{
diff --git a/src/WebJobs.Script/WebJobs.Script.csproj b/src/WebJobs.Script/WebJobs.Script.csproj
index bcccab5b9c..2117fb7ad7 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..c8a7f629a0 100644
--- a/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptions.cs
+++ b/src/WebJobs.Script/Workers/Http/Configuration/HttpWorkerOptions.cs
@@ -15,8 +15,19 @@ public class HttpWorkerOptions
public int Port { get; set; }
+ ///
+ /// Gets or sets a value indicating whether the host will forward the request to the worker process.
+ ///
+ ///
+ /// The host will rebuild the initial invocation HTTP Request and send the copy to the worker process.
+ ///
public bool EnableForwardingHttpRequest { get; set; }
+ ///
+ /// Gets or sets a value indicating whether the host will proxy the invocation HTTP request to the worker process.
+ ///
+ 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..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 maitaining 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 825a8c24c7..aea4ee70f0 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.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -24,17 +25,23 @@ public class DefaultHttpWorkerService : IHttpWorkerService
private readonly HttpWorkerOptions _httpWorkerOptions;
private readonly ILogger _logger;
private readonly bool _enableRequestTracing;
+ private readonly IHttpProxyService _httpProxyService;
+ private readonly Uri _destinationPrefix;
+ private readonly string _userAgentString;
- 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)
{
@@ -47,6 +54,9 @@ internal DefaultHttpWorkerService(HttpClient httpClient, IOptions httpWorkerOptions)
@@ -61,16 +71,44 @@ 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)
{
return ProcessHttpInAndOutInvocationRequest(scriptInvocationContext);
}
- return ProcessDefaultInvocationRequest(scriptInvocationContext);
}
+
return ProcessDefaultInvocationRequest(scriptInvocationContext);
}
+ internal async Task ProxyInvocationRequest(ScriptInvocationContext scriptInvocationContext)
+ {
+ try
+ {
+ if (!scriptInvocationContext.TryGetHttpRequest(out HttpRequest httpRequest))
+ {
+ throw new InvalidOperationException($"Cannot proxy the HttpTrigger function {scriptInvocationContext.FunctionMetadata.Name} without an input of type {nameof(HttpRequest)}.");
+ }
+
+ AddProxyingHeaders(httpRequest, scriptInvocationContext.ExecutionContext.InvocationId.ToString());
+
+ // 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); // this will throw if forwarding is unsuccessful
+ scriptInvocationContext.ResultSource.SetResult(ScriptInvocationResult.Success);
+ }
+ catch (Exception exc)
+ {
+ scriptInvocationContext.ResultSource.TrySetException(exc);
+ }
+ }
+
internal async Task ProcessHttpInAndOutInvocationRequest(ScriptInvocationContext scriptInvocationContext)
{
_logger.CustomHandlerForwardingHttpTriggerInvocation(scriptInvocationContext.FunctionMetadata.Name, scriptInvocationContext.ExecutionContext.InvocationId);
@@ -162,7 +200,14 @@ 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);
+ }
+
+ private void AddProxyingHeaders(HttpRequest httpRequest, string invocationId)
+ {
+ // if there are existing headers, override them
+ httpRequest.Headers[HttpWorkerConstants.HostVersionHeaderName] = ScriptHost.Version;
+ httpRequest.Headers[HttpWorkerConstants.InvocationIdHeaderName] = invocationId;
}
internal string GetPathValue(HttpWorkerOptions httpWorkerOptions, string functionName, HttpRequest httpRequest)
diff --git a/test/WebJobs.Script.Tests.Integration/ApplicationInsights/ApplicationInsightsTestFixture.cs b/test/WebJobs.Script.Tests.Integration/ApplicationInsights/ApplicationInsightsTestFixture.cs
index a927b6a82b..29becaa359 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.Http;
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/HttpProxyService/DefaultHttpProxyServiceTests.cs b/test/WebJobs.Script.Tests/HttpProxyService/DefaultHttpProxyServiceTests.cs
index 3356ed2017..81313edb38 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.Http;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
diff --git a/test/WebJobs.Script.Tests/HttpWorker/DefaultHttpWorkerServiceTests.cs b/test/WebJobs.Script.Tests/HttpWorker/DefaultHttpWorkerServiceTests.cs
index 66435a166c..4e28afac1d 100644
--- a/test/WebJobs.Script.Tests/HttpWorker/DefaultHttpWorkerServiceTests.cs
+++ b/test/WebJobs.Script.Tests/HttpWorker/DefaultHttpWorkerServiceTests.cs
@@ -12,9 +12,13 @@
using System.Threading;
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.Http;
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,6 +43,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 +59,7 @@ public DefaultHttpWorkerServiceTests()
{
FunctionTimeout = TimeSpan.FromMinutes(15)
};
+ _mockHttpProxyService = new Mock(MockBehavior.Strict);
}
public static IEnumerable