-
Notifications
You must be signed in to change notification settings - Fork 188
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
mapping endpoints on worker for ASP.NET proxy integration (#1596)
- Loading branch information
Showing
7 changed files
with
348 additions
and
10 deletions.
There are no files selected for viewing
19 changes: 19 additions & 0 deletions
19
extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/FunctionHttpBinding.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
||
namespace Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore | ||
{ | ||
/// <summary> | ||
/// Represents an HttpTrigger binding. Internal class for deserializing raw binding info. | ||
/// </summary> | ||
internal class FunctionHttpBinding | ||
{ | ||
public string Name { get; set; } = default!; | ||
|
||
public string Type { get; set; } = default!; | ||
|
||
public string Route { get; set; } = default!; | ||
|
||
public string[] Methods { get; set; } = default!; | ||
} | ||
} |
137 changes: 137 additions & 0 deletions
137
...ons/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/FunctionsEndpointDataSource.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Text.Json; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Routing; | ||
using Microsoft.AspNetCore.Routing.Patterns; | ||
using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata; | ||
using Microsoft.Extensions.FileProviders; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
namespace Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.AspNetMiddleware | ||
{ | ||
internal class FunctionsEndpointDataSource : EndpointDataSource | ||
{ | ||
private const string FunctionsApplicationDirectoryKey = "FUNCTIONS_APPLICATION_DIRECTORY"; | ||
private const string HostJsonFileName = "host.json"; | ||
private const string DefaultRoutePrefix = "api"; | ||
|
||
private readonly IFunctionMetadataProvider _functionMetadataProvider; | ||
private readonly object _lock = new(); | ||
|
||
private static readonly JsonSerializerOptions _jsonSerializerOptions = new() | ||
{ | ||
PropertyNameCaseInsensitive = true | ||
}; | ||
|
||
|
||
private List<Endpoint>? _endpoints; | ||
|
||
public FunctionsEndpointDataSource(IFunctionMetadataProvider functionMetadataProvider) | ||
{ | ||
_functionMetadataProvider = functionMetadataProvider ?? throw new ArgumentNullException(nameof(functionMetadataProvider)); | ||
} | ||
|
||
public override IReadOnlyList<Endpoint> Endpoints | ||
{ | ||
get | ||
{ | ||
if (_endpoints is null) | ||
{ | ||
lock (_lock) | ||
{ | ||
_endpoints ??= BuildEndpoints(); | ||
} | ||
} | ||
|
||
return _endpoints; | ||
} | ||
} | ||
|
||
private List<Endpoint> BuildEndpoints() | ||
{ | ||
List<Endpoint> endpoints = new List<Endpoint>(); | ||
|
||
string scriptRoot = Environment.GetEnvironmentVariable(FunctionsApplicationDirectoryKey) ?? | ||
throw new InvalidOperationException("Cannot determine script root directory."); | ||
|
||
var metadata = _functionMetadataProvider.GetFunctionMetadataAsync(scriptRoot).GetAwaiter().GetResult(); | ||
|
||
string routePrefix = GetRoutePrefixFromHostJson(scriptRoot) ?? DefaultRoutePrefix; | ||
|
||
foreach (var functionMetadata in metadata) | ||
{ | ||
var endpoint = MapHttpFunction(functionMetadata, routePrefix); | ||
|
||
if (endpoint is not null) | ||
{ | ||
endpoints.Add(endpoint); | ||
} | ||
} | ||
|
||
return endpoints; | ||
} | ||
|
||
public override IChangeToken GetChangeToken() => NullChangeToken.Singleton; | ||
|
||
internal static Endpoint? MapHttpFunction(IFunctionMetadata functionMetadata, string routePrefix) | ||
{ | ||
if (functionMetadata.RawBindings is null) | ||
{ | ||
return null; | ||
} | ||
|
||
var functionName = functionMetadata.Name ?? string.Empty; | ||
|
||
int order = 0; | ||
foreach (var binding in functionMetadata.RawBindings) | ||
{ | ||
var functionBinding = JsonSerializer.Deserialize<FunctionHttpBinding>(binding, _jsonSerializerOptions); | ||
|
||
if (functionBinding is null) | ||
{ | ||
continue; | ||
} | ||
|
||
if (functionBinding.Type.Equals("httpTrigger", StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
string routeSuffix = functionBinding.Route ?? functionName; | ||
string route = $"{routePrefix}/{routeSuffix}"; | ||
|
||
var pattern = RoutePatternFactory.Parse(route); | ||
|
||
var endpointBuilder = new RouteEndpointBuilder(FunctionsHttpContextExtensions.InvokeFunctionAsync, pattern, order++) | ||
{ | ||
DisplayName = functionName | ||
}; | ||
endpointBuilder.Metadata.Add(new HttpMethodMetadata(functionBinding.Methods)); | ||
|
||
// no need to look at other bindings for this function | ||
return endpointBuilder.Build(); | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private static string? GetRoutePrefixFromHostJson(string scriptRoot) | ||
{ | ||
string hostJsonPath = Path.Combine(scriptRoot, HostJsonFileName); | ||
|
||
if (!File.Exists(hostJsonPath)) | ||
{ | ||
return null; | ||
} | ||
|
||
string hostJsonString = File.ReadAllText(hostJsonPath); | ||
return GetRoutePrefix(hostJsonString); | ||
} | ||
|
||
internal static string? GetRoutePrefix(string hostJsonString) | ||
{ | ||
var hostJson = JsonSerializer.Deserialize<HostJsonModel>(hostJsonString, _jsonSerializerOptions); | ||
return hostJson?.Extensions?.Http?.RoutePrefix; | ||
} | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
.../Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/FunctionsHttpContextExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT License. See License.txt in the project root for license information. | ||
|
||
using System.Threading.Tasks; | ||
using Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
namespace Microsoft.AspNetCore.Http | ||
{ | ||
internal static class FunctionsHttpContextExtensions | ||
{ | ||
internal static Task InvokeFunctionAsync(this HttpContext context) | ||
{ | ||
var coordinator = context.RequestServices.GetRequiredService<IHttpCoordinator>(); | ||
context.Request.Headers.TryGetValue(Constants.CorrelationHeader, out StringValues invocationId); | ||
return coordinator.RunFunctionInvocationAsync(invocationId); | ||
} | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/HostJsonModel.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
namespace Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore | ||
{ | ||
/// <summary> | ||
/// Represents host.json. Internal class for deserializing. | ||
/// </summary> | ||
internal class HostJsonModel | ||
{ | ||
public HostJsonExtensionModel Extensions { get; set; } = default!; | ||
} | ||
|
||
internal class HostJsonExtensionModel | ||
{ | ||
public HostJsonExtensionHttpModel Http { get; set; } = default!; | ||
} | ||
|
||
internal class HostJsonExtensionHttpModel | ||
{ | ||
public string RoutePrefix { get; set; } = default!; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.