Skip to content

Commit

Permalink
Include route params in RpcHttp instance when http proxying is enable…
Browse files Browse the repository at this point in the history
…d. (#9997) (#10072)

* Include route params in RpcHttp instance when http proxying is enabled.

* Fixes to address PR comments.
  • Loading branch information
kshyju committed Apr 27, 2024
1 parent 6c98948 commit 2462997
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,26 @@ internal static TypedData ToModelBindingDataArray(this ParameterBindingData[] da

internal static async Task<TypedData> ToRpcHttp(this HttpRequest request, ILogger logger, GrpcCapabilities capabilities)
{
bool requiresRouteParams = !string.IsNullOrEmpty(capabilities.GetCapabilityState(RpcWorkerConstants.RequiresRouteParameters));
bool isHttpProxying = !string.IsNullOrEmpty(capabilities.GetCapabilityState(RpcWorkerConstants.HttpUri));
bool shouldUseNullableValueDictionary = ShouldUseNullableValueDictionary(capabilities);

// If proxying the http request to the worker and
// worker requesting route params, send an empty rpc http object with only params populated.
if (isHttpProxying && requiresRouteParams)
{
var typedDataWithRouteParams = new TypedData
{
Http = new RpcHttp()
};

PopulateHttpRouteDataAsParams(request, typedDataWithRouteParams.Http, shouldUseNullableValueDictionary);

return typedDataWithRouteParams;
}

// If proxying the http request to the worker, keep the grpc message minimal
bool skipHttpInputs = !string.IsNullOrEmpty(capabilities.GetCapabilityState(RpcWorkerConstants.HttpUri));
if (skipHttpInputs)
if (isHttpProxying)
{
return EmptyRpcHttp;
}
Expand All @@ -119,7 +136,6 @@ internal static async Task<TypedData> ToRpcHttp(this HttpRequest request, ILogge
Http = http
};

var shouldUseNullableValueDictionary = ShouldUseNullableValueDictionary(capabilities);
foreach (var pair in request.Query)
{
var value = pair.Value.ToString();
Expand Down Expand Up @@ -153,24 +169,7 @@ internal static async Task<TypedData> ToRpcHttp(this HttpRequest request, ILogge
}
}

if (request.HttpContext.Items.TryGetValue(HttpExtensionConstants.AzureWebJobsHttpRouteDataKey, out object routeData))
{
Dictionary<string, object> parameters = (Dictionary<string, object>)routeData;
foreach (var pair in parameters)
{
if (pair.Value != null)
{
if (shouldUseNullableValueDictionary)
{
http.NullableParams.Add(pair.Key, new NullableString { Value = pair.Value.ToString() });
}
else
{
http.Params.Add(pair.Key, pair.Value.ToString());
}
}
}
}
PopulateHttpRouteDataAsParams(request, http, shouldUseNullableValueDictionary);

// parse ClaimsPrincipal if exists
if (request.HttpContext?.User?.Identities != null)
Expand Down Expand Up @@ -222,6 +221,30 @@ internal static async Task<TypedData> ToRpcHttp(this HttpRequest request, ILogge
return typedData;
}

private static void PopulateHttpRouteDataAsParams(HttpRequest request, RpcHttp http, bool shouldUseNullableValueDictionary)
{
if (!request.HttpContext.Items.TryGetValue(HttpExtensionConstants.AzureWebJobsHttpRouteDataKey, out var routeData)
|| routeData is not Dictionary<string, object> parameters)
{
return;
}

foreach (var pair in parameters)
{
if (pair.Value != null)
{
if (shouldUseNullableValueDictionary)
{
http.NullableParams.Add(pair.Key, new NullableString { Value = pair.Value.ToString() });
}
else
{
http.Params.Add(pair.Key, pair.Value.ToString());
}
}
}
}

private static async Task PopulateBody(HttpRequest request, RpcHttp http, GrpcCapabilities capabilities, ILogger logger)
{
object body = null;
Expand Down
5 changes: 5 additions & 0 deletions src/WebJobs.Script/Workers/Rpc/RpcWorkerConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ public static class RpcWorkerConstants
public const string WorkerApplicationInsightsLoggingEnabled = nameof(WorkerApplicationInsightsLoggingEnabled);
public const string HttpUri = "HttpUri";

/// <summary>
/// Indicates whether the RpcHttp request should include the route parameters when the request is being proxied to an HTTP worker.
/// </summary>
public const string RequiresRouteParameters = "RequiresRouteParameters";

/// <summary>
/// Indicates whether empty entries in the trigger message should be included when sending RpcInvocation data to OOP workers.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Threading.Tasks;
using Google.Protobuf.Collections;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Script.Description;
using Microsoft.Azure.WebJobs.Script.Grpc;
using Microsoft.Azure.WebJobs.Script.Grpc.Messages;
Expand Down Expand Up @@ -332,16 +333,52 @@ public async Task ToRpc_Http()
public async Task ToRpc_Http_WithProxy()
{
// Specify that we're using proxies.
var rpcHttp = await CreateTestRpcHttp(new Dictionary<string, string>() { { "HttpUri", "something" } });
var rpcHttp = await CreateTestRpcHttp(new Dictionary<string, string>() { { RpcWorkerConstants.HttpUri, "something" } });

// everything should come back empty
Assert.Empty(rpcHttp.Url);
Assert.Empty(rpcHttp.Headers);
Assert.Empty(rpcHttp.Query);
Assert.Null(rpcHttp.Body);
Assert.Empty(rpcHttp.Params);
Assert.Empty(rpcHttp.NullableParams);
}

private async Task<RpcHttp> CreateTestRpcHttp(IDictionary<string, string> capabilities = null)
[Fact]
public async Task ToRpc_Http_WithProxy_HandleRouteParams_Capability()
{
// Specify that we're using proxies & worker can handle route params.
var workerCapabilities = new Dictionary<string, string>()
{
{ RpcWorkerConstants.HttpUri, "http://localhost:1234" },
{ RpcWorkerConstants.RequiresRouteParameters, bool.TrueString }
};

var routeDataValues = new Dictionary<string, object>
{
{ "category", "computer" },
{ "brand", "microsoft" }
};
var httpContextItems = new Dictionary<object, object>
{
[HttpExtensionConstants.AzureWebJobsHttpRouteDataKey] = routeDataValues
};

var rpcHttp = await CreateTestRpcHttp(workerCapabilities, httpContextItems);

// Ensure the response includes route params
Assert.Equal(2, rpcHttp.Params.Count);
Assert.Equal("computer", rpcHttp.Params["category"]);
Assert.Equal("microsoft", rpcHttp.Params["brand"]);

// everything else should come back empty
Assert.Empty(rpcHttp.Url);
Assert.Empty(rpcHttp.Headers);
Assert.Empty(rpcHttp.Query);
Assert.Null(rpcHttp.Body);
}

private async Task<RpcHttp> CreateTestRpcHttp(IDictionary<string, string> capabilities = null, IDictionary<object, object> httpRequestContextItems = null)
{
var logger = new TestLogger("test");
GrpcCapabilities grpcCapabilities = new GrpcCapabilities(logger);
Expand All @@ -354,6 +391,14 @@ private async Task<RpcHttp> CreateTestRpcHttp(IDictionary<string, string> capabi
headers.Add("test-header", "test value");
var request = HttpTestHelpers.CreateHttpRequest("POST", "http://local/test?a=b", headers: headers, body: "test body");

if (httpRequestContextItems is not null)
{
foreach (var item in httpRequestContextItems)
{
request.HttpContext.Items.Add(item.Key, item.Value);
}
}

var bindingData = new Dictionary<string, object>
{
{ "req", request },
Expand Down

0 comments on commit 2462997

Please sign in to comment.