Skip to content

Commit

Permalink
sending an empty rpchttp to worker when proxying (Azure#9415)
Browse files Browse the repository at this point in the history
  • Loading branch information
brettsam authored Jul 24, 2023
1 parent 6b79fa2 commit 388c4d2
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 12 deletions.
3 changes: 2 additions & 1 deletion release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@
- Microsoft.IdentityModel.JsonWebTokens
- Microsoft.IdentityModel.Logging
- Updated Grpc.AspNetCore package to 2.55.0 (https://github.com/Azure/azure-functions-host/pull/9373)
- Update protobuf file to v1.10.0 (https://github.com/Azure/azure-functions-host/pull/9405)
- Update protobuf file to v1.10.0 (https://github.com/Azure/azure-functions-host/pull/9405)
- Send an empty RpcHttp payload if proxying the http request to the worker (https://github.com/Azure/azure-functions-host/pull/9415)
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
using System.Text;
using System.Threading.Tasks;
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Script.Description;
using Microsoft.Azure.WebJobs.Script.Extensions;
using Microsoft.Azure.WebJobs.Script.Grpc.Messages;
Expand All @@ -25,19 +23,20 @@ namespace Microsoft.Azure.WebJobs.Script.Grpc
internal static class GrpcMessageConversionExtensions
{
private static readonly JsonSerializerSettings _datetimeSerializerSettings = new JsonSerializerSettings { DateParseHandling = DateParseHandling.None };
private static readonly TypedData EmptyRpcHttp = new() { Http = new() };

public static object ToObject(this TypedData typedData) =>
typedData.DataCase switch
{
RpcDataType.None => null,
RpcDataType.String => typedData.String,
RpcDataType.Json => JsonConvert.DeserializeObject(typedData.Json, _datetimeSerializerSettings),
RpcDataType.Bytes or RpcDataType.Stream => typedData.Bytes.ToByteArray(),
RpcDataType.Http => GrpcMessageExtensionUtilities.ConvertFromHttpMessageToExpando(typedData.Http),
RpcDataType.Int => typedData.Int,
RpcDataType.Double => typedData.Double,
// TODO better exception
_ => throw new InvalidOperationException($"Unknown RpcDataType: {typedData.DataCase}")
RpcDataType.None => null,
RpcDataType.String => typedData.String,
RpcDataType.Json => JsonConvert.DeserializeObject(typedData.Json, _datetimeSerializerSettings),
RpcDataType.Bytes or RpcDataType.Stream => typedData.Bytes.ToByteArray(),
RpcDataType.Http => GrpcMessageExtensionUtilities.ConvertFromHttpMessageToExpando(typedData.Http),
RpcDataType.Int => typedData.Int,
RpcDataType.Double => typedData.Double,
// TODO better exception
_ => throw new InvalidOperationException($"Unknown RpcDataType: {typedData.DataCase}")
};

public static ValueTask<TypedData> ToRpc(this object value, ILogger logger, GrpcCapabilities capabilities)
Expand Down Expand Up @@ -102,6 +101,13 @@ internal static TypedData ToModelBindingDataArray(this ParameterBindingData[] da

internal static async Task<TypedData> ToRpcHttp(this HttpRequest request, ILogger logger, GrpcCapabilities capabilities)
{
// If proxying the http request to the worker, keep the grpc message minimal
bool skipHttpInputs = !string.IsNullOrEmpty(capabilities.GetCapabilityState(RpcWorkerConstants.HttpUri));
if (skipHttpInputs)
{
return EmptyRpcHttp;
}

var http = new RpcHttp()
{
Url = $"{(request.IsHttps ? "https" : "http")}://{request.Host}{request.Path}{request.QueryString}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using Microsoft.Azure.WebJobs.Script.Workers.SharedMemoryDataTransfer;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.WebJobs.Script.Tests;
using Newtonsoft.Json.Linq;
using Xunit;

Expand Down Expand Up @@ -316,6 +317,94 @@ public async Task ToRpcInvocationRequest_MultipleInputBindings()
Assert.True(result.TriggerMetadata.ContainsKey("query"));
}

[Fact]
public async Task ToRpc_Http()
{
var rpcHttp = await CreateTestRpcHttp();

Assert.Equal("http://local/test?a=b", rpcHttp.Url);
Assert.Equal("test value", rpcHttp.Headers["test-header"]);
Assert.Equal("b", rpcHttp.Query["a"]);
Assert.Equal("test body", rpcHttp.Body.String);
}

[Fact]
public async Task ToRpc_Http_WithProxy()
{
// Specify that we're using proxies.
var rpcHttp = await CreateTestRpcHttp(new Dictionary<string, string>() { { "HttpUri", "something" } });

// everything 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)
{
var logger = new TestLogger("test");
GrpcCapabilities grpcCapabilities = new GrpcCapabilities(logger);
if (capabilities is not null)
{
grpcCapabilities.UpdateCapabilities(capabilities);
}

var headers = new HeaderDictionary();
headers.Add("test-header", "test value");
var request = HttpTestHelpers.CreateHttpRequest("POST", "http://local/test?a=b", headers: headers, body: "test body");

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

var inputs = new List<(string Name, DataType Type, object Val)>
{
("req", DataType.String, request),
};

var invocationContext = new ScriptInvocationContext()
{
ExecutionContext = new ExecutionContext()
{
InvocationId = Guid.NewGuid(),
FunctionName = "Test",
},
BindingData = bindingData,
Inputs = inputs,
ResultSource = new TaskCompletionSource<ScriptInvocationResult>(),
Logger = logger,
AsyncExecutionContext = System.Threading.ExecutionContext.Capture()
};

var functionMetadata = new FunctionMetadata
{
Name = "Test"
};

var httpTriggerBinding = new BindingMetadata
{
Name = "req",
Type = "httpTrigger",
Direction = BindingDirection.In,
Raw = new JObject()
};

functionMetadata.Bindings.Add(httpTriggerBinding);
invocationContext.FunctionMetadata = functionMetadata;

var result = await invocationContext.ToRpcInvocationRequest(logger, grpcCapabilities, isSharedMemoryDataTransferEnabled: false, _sharedMemoryManager);
var resultHttp = result.InputData[0].Data.Http;
Assert.Equal(1, result.TriggerMetadata.Count);
Assert.Same(resultHttp, result.TriggerMetadata["req"].Http);

Assert.Equal(1, result.InputData.Count);
Assert.Equal("req", result.InputData[0].Name);

return resultHttp;
}

[Fact]
public void TestSetRetryContext_NoRetry()
{
Expand Down

0 comments on commit 388c4d2

Please sign in to comment.