-
Notifications
You must be signed in to change notification settings - Fork 457
Custom Handler Proxying #11035
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Custom Handler Proxying #11035
Changes from all commits
555c0fa
6eac80d
165f2d2
7b0a7c5
4133b2a
ef032c5
436c6ed
944e1f5
2256ee4
a05018b
73b561c
d0ad9a9
82f1226
8bb0e1b
e4e5a9a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
||
|
@@ -24,17 +25,24 @@ public class DefaultHttpWorkerService : IHttpWorkerService | |
private readonly HttpWorkerOptions _httpWorkerOptions; | ||
private readonly ILogger _logger; | ||
private readonly bool _enableRequestTracing; | ||
|
||
public DefaultHttpWorkerService(IOptions<HttpWorkerOptions> httpWorkerOptions, ILoggerFactory loggerFactory, IEnvironment environment, IOptions<ScriptJobHostOptions> scriptHostOptions) | ||
: this(CreateHttpClient(httpWorkerOptions), httpWorkerOptions, loggerFactory.CreateLogger<DefaultHttpWorkerService>(), environment, scriptHostOptions) | ||
private readonly IHttpProxyService _httpProxyService; | ||
private readonly ScriptInvocationResult _successfulInvocationResult; | ||
private readonly Uri _destinationPrefix; | ||
private readonly string _userAgentString; | ||
|
||
public DefaultHttpWorkerService(IOptions<HttpWorkerOptions> httpWorkerOptions, ILoggerFactory loggerFactory, IEnvironment environment, | ||
IOptions<ScriptJobHostOptions> scriptHostOptions, IHttpProxyService httpProxyService) | ||
: this(CreateHttpClient(httpWorkerOptions), httpWorkerOptions, loggerFactory.CreateLogger<DefaultHttpWorkerService>(), environment, scriptHostOptions, httpProxyService) | ||
{ | ||
} | ||
|
||
internal DefaultHttpWorkerService(HttpClient httpClient, IOptions<HttpWorkerOptions> httpWorkerOptions, ILogger logger, IEnvironment environment, IOptions<ScriptJobHostOptions> scriptHostOptions) | ||
internal DefaultHttpWorkerService(HttpClient httpClient, IOptions<HttpWorkerOptions> httpWorkerOptions, ILogger logger, IEnvironment environment, | ||
IOptions<ScriptJobHostOptions> 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 +55,14 @@ internal DefaultHttpWorkerService(HttpClient httpClient, IOptions<HttpWorkerOpti | |
// Set 1 minute greater than FunctionTimeout to ensure invoction failure due to timeout is raised before httpClient raises operation cancelled exception | ||
_httpClient.Timeout = scriptHostOptions.Value.FunctionTimeout.Value.Add(TimeSpan.FromMinutes(1)); | ||
} | ||
|
||
_successfulInvocationResult = new ScriptInvocationResult() | ||
{ | ||
Outputs = new Dictionary<string, object>() | ||
}; | ||
|
||
_destinationPrefix = new UriBuilder(WorkerConstants.HttpScheme, WorkerConstants.HostName, _httpWorkerOptions.Port).Uri; | ||
_userAgentString = $"{HttpWorkerConstants.UserAgentHeaderValue}/{ScriptHost.Version}"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this ever get set outside of the ctor? Could this be a const? It doesnt look like its made up of anything that would require it to be init in here no? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't get set outside of the ctor, but |
||
} | ||
|
||
private static HttpClient CreateHttpClient(IOptions<HttpWorkerOptions> httpWorkerOptions) | ||
|
@@ -61,6 +77,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 +92,29 @@ public Task InvokeAsync(ScriptInvocationContext 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); | ||
scriptInvocationContext.ResultSource.SetResult(_successfulInvocationResult); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the downstream code updates this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, the downstream code does not. This value is set to allow the function invocation to continue, but its value is never used (similar to how http proxying flows). We ignore the function invocation result at this step so that the actual response just flows through. |
||
} | ||
catch (Exception exc) | ||
{ | ||
scriptInvocationContext.ResultSource.TrySetException(exc); | ||
} | ||
} | ||
|
||
internal async Task ProcessHttpInAndOutInvocationRequest(ScriptInvocationContext scriptInvocationContext) | ||
{ | ||
_logger.CustomHandlerForwardingHttpTriggerInvocation(scriptInvocationContext.FunctionMetadata.Name, scriptInvocationContext.ExecutionContext.InvocationId); | ||
|
@@ -162,7 +206,15 @@ 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; | ||
httpRequest.Headers.UserAgent = _userAgentString; | ||
} | ||
|
||
internal string GetPathValue(HttpWorkerOptions httpWorkerOptions, string functionName, HttpRequest httpRequest) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be conditionally registered? Only when custom handlers is enabled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the cleanup and refactor, this service will also be used for HTTP Proxying for worker http streaming. Information about streaming support/capabilities won't be known until there is a worker init response (which occurs much later than this stage).