diff --git a/src/main/java/com/microsoft/azure/functions/worker/Constants.java b/src/main/java/com/microsoft/azure/functions/worker/Constants.java index ed5a1e32..ea5d5404 100644 --- a/src/main/java/com/microsoft/azure/functions/worker/Constants.java +++ b/src/main/java/com/microsoft/azure/functions/worker/Constants.java @@ -9,4 +9,5 @@ private Constants(){} public final static String JAVA_LIBRARY_DIRECTORY = "/annotationLib"; public final static String JAVA_LIBRARY_ARTIFACT_ID = "azure-functions-java-library"; public final static String HAS_IMPLICIT_OUTPUT_QUALIFIED_NAME = "com.microsoft.azure.functions.annotation.HasImplicitOutput"; + public final static String NULLABLE_VALUES_ENABLED_APP_SETTING = "FUNCTIONS_WORKER_NULLABLE_VALUES_ENABLED"; } diff --git a/src/main/java/com/microsoft/azure/functions/worker/binding/RpcHttpRequestDataSource.java b/src/main/java/com/microsoft/azure/functions/worker/binding/RpcHttpRequestDataSource.java index 056c9fbc..3fd67f6f 100644 --- a/src/main/java/com/microsoft/azure/functions/worker/binding/RpcHttpRequestDataSource.java +++ b/src/main/java/com/microsoft/azure/functions/worker/binding/RpcHttpRequestDataSource.java @@ -6,7 +6,11 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import com.microsoft.azure.functions.rpc.messages.NullableTypes; +import com.microsoft.azure.functions.worker.Constants; +import com.microsoft.azure.functions.worker.Util; import org.apache.commons.lang3.reflect.TypeUtils; import com.microsoft.azure.functions.HttpMethod; @@ -22,8 +26,10 @@ public RpcHttpRequestDataSource(String name, RpcHttp value) { super(name, null, HTTP_DATA_OPERATIONS); this.httpPayload = value; this.bodyDataSource = BindingDataStore.rpcSourceFromTypedData(null, this.httpPayload.getBody()); - this.fields = Arrays.asList(this.httpPayload.getHeadersMap(), this.httpPayload.getQueryMap(), - this.httpPayload.getParamsMap()); + this.fields = Arrays.asList( + convertFromNullableMap(this.httpPayload.getNullableHeadersMap()), + convertFromNullableMap(this.httpPayload.getNullableQueryMap()), + convertFromNullableMap(this.httpPayload.getNullableParamsMap())); this.setValue(this); } @@ -45,12 +51,12 @@ public HttpMethod getHttpMethod() { @Override public Map getHeaders() { - return this.parentDataSource.httpPayload.getHeadersMap(); + return convertFromNullableMap(this.parentDataSource.httpPayload.getNullableHeadersMap()); } @Override public Map getQueryParameters() { - return this.parentDataSource.httpPayload.getQueryMap(); + return convertFromNullableMap(this.parentDataSource.httpPayload.getNullableQueryMap()); } @Override @@ -86,4 +92,16 @@ public Builder createResponseBuilder(HttpStatus status) { return new HttpRequestMessageImpl(v, bodyData.getValue()); }); } + + private static Map convertFromNullableMap(Map nullableMap) { + if (Util.isTrue(System.getenv(Constants.NULLABLE_VALUES_ENABLED_APP_SETTING))) { + return nullableMap.entrySet().stream().collect( + Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getValue()) + ); + } else { + return nullableMap.entrySet().stream() + .filter(e -> e.getValue().getValue() != null && !e.getValue().getValue().trim().isEmpty()) + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getValue())); + } + } } \ No newline at end of file diff --git a/src/main/java/com/microsoft/azure/functions/worker/handler/WorkerInitRequestHandler.java b/src/main/java/com/microsoft/azure/functions/worker/handler/WorkerInitRequestHandler.java index 9ef511e8..75d3b96e 100644 --- a/src/main/java/com/microsoft/azure/functions/worker/handler/WorkerInitRequestHandler.java +++ b/src/main/java/com/microsoft/azure/functions/worker/handler/WorkerInitRequestHandler.java @@ -23,6 +23,7 @@ String execute(WorkerInitRequest request, WorkerInitResponse.Builder response) { response.putCapabilities("TypedDataCollection", "TypedDataCollection"); response.putCapabilities("WorkerStatus", "WorkerStatus"); response.putCapabilities("RpcHttpBodyOnly", "RpcHttpBodyOnly"); + response.putCapabilities("UseNullableValueDictionaryForHttp", "UseNullableValueDictionaryForHttp"); response.putCapabilities("RpcHttpTriggerMetadataRemoved", "RpcHttpTriggerMetadataRemoved"); response.putCapabilities("HandlesWorkerTerminateMessage", "HandlesWorkerTerminateMessage"); response.setWorkerMetadata(composeWorkerMetadata()); diff --git a/src/test/java/com/microsoft/azure/functions/worker/binding/tests/RpcHttpRequestDataSourceTest.java b/src/test/java/com/microsoft/azure/functions/worker/binding/tests/RpcHttpRequestDataSourceTest.java index 813f4fa4..7ab659c1 100644 --- a/src/test/java/com/microsoft/azure/functions/worker/binding/tests/RpcHttpRequestDataSourceTest.java +++ b/src/test/java/com/microsoft/azure/functions/worker/binding/tests/RpcHttpRequestDataSourceTest.java @@ -5,15 +5,20 @@ import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.*; + +import com.microsoft.azure.functions.rpc.messages.NullableTypes.NullableString; import com.microsoft.azure.functions.HttpRequestMessage; import com.microsoft.azure.functions.HttpStatus; import com.microsoft.azure.functions.rpc.messages.RpcHttp; import com.microsoft.azure.functions.rpc.messages.TypedData; +import com.microsoft.azure.functions.worker.Constants; import com.microsoft.azure.functions.worker.binding.*; +import com.microsoft.azure.functions.worker.broker.JavaFunctionBroker; +import com.microsoft.azure.functions.worker.handler.FunctionEnvironmentReloadRequestHandler; +import com.microsoft.azure.functions.worker.reflect.DefaultClassLoaderProvider; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; - +import static org.junit.jupiter.api.Assertions.*; public class RpcHttpRequestDataSourceTest { @@ -26,13 +31,23 @@ public void HttpRequestIntBody(HttpRequestMessage request) { public void HttpRequestBinaryBody(HttpRequestMessage request) { } - public static RpcHttp getTestRpcHttp(Object inputBody) throws Exception { + public static RpcHttp getTestRpcHttp( + Object inputBody, + Map headersMap, + Map queryMap) throws Exception { TypedData.Builder dataBuilder = TypedData.newBuilder(); RpcHttp.Builder httpBuilder = RpcHttp.newBuilder() .setStatusCode(Integer.toString(HttpStatus.OK.value())); - Map headers = new HashMap<>(); - headers.put("header", "testHeader"); - headers.forEach(httpBuilder::putHeaders); + if (headersMap != null) { + for (String key : headersMap.keySet()) { + httpBuilder.putNullableHeaders(key, NullableString.newBuilder().setValue(headersMap.get(key)).build()); + } + } + if (queryMap != null) { + for (String key : queryMap.keySet()) { + httpBuilder.putNullableQuery(key, NullableString.newBuilder().setValue(queryMap.get(key)).build()); + } + } RpcUnspecifiedDataTarget bodyTarget = new RpcUnspecifiedDataTarget(); Object body = inputBody; bodyTarget.setValue(body); @@ -42,13 +57,81 @@ public static RpcHttp getTestRpcHttp(Object inputBody) throws Exception { } @Test - public void rpcHttpDataSource_To_HttpRequestMessage_StringBody() throws Exception { + public void rpcHttpDataSourceToHttpRequestMessageEnvSettingEnabled() throws Exception { + DefaultClassLoaderProvider classLoader = new DefaultClassLoaderProvider(); + JavaFunctionBroker broker = new JavaFunctionBroker(classLoader); + FunctionEnvironmentReloadRequestHandler envHandler = new FunctionEnvironmentReloadRequestHandler(broker); + Map existingVariables = System.getenv(); + Map newEnvVariables = new HashMap<>(); + newEnvVariables.putAll(existingVariables); + newEnvVariables.put(Constants.NULLABLE_VALUES_ENABLED_APP_SETTING, "true"); + envHandler.setEnv(newEnvVariables); + Method httpRequestMessageStringBodyMethod = getFunctionMethod("HttpRequestStringBody"); + Map queryMap = new HashMap() {{ + put("name", ""); + put("count", "1"); + }}; + Map headersMap = new HashMap() {{ + put("cookie", ""); + put("accept-encoding", "gzip, deflate, br"); + }}; + Parameter[] parameters = httpRequestMessageStringBodyMethod.getParameters(); + String sourceKey = "testRpcHttp"; + RpcHttp input = getTestRpcHttp(null, headersMap, queryMap); + RpcHttpRequestDataSource rpcHttp = new RpcHttpRequestDataSource(sourceKey, input); + Optional actualBindingData = rpcHttp.computeByName(sourceKey, + parameters[0].getParameterizedType()); + BindingData actualArg = actualBindingData.orElseThrow(WrongMethodTypeException::new); + HttpRequestMessage requestMsg = (HttpRequestMessage) actualArg.getValue(); + + assertTrue(requestMsg.getQueryParameters().get("name").isEmpty()); + assertEquals("1", requestMsg.getQueryParameters().get("count")); + assertTrue(requestMsg.getHeaders().get("cookie").isEmpty()); + assertEquals("gzip, deflate, br", requestMsg.getHeaders().get("accept-encoding")); + } + + @Test + public void rpcHttpDataSourceToHttpRequestMessageEnvSettingDisabled() throws Exception { + DefaultClassLoaderProvider classLoader = new DefaultClassLoaderProvider(); + JavaFunctionBroker broker = new JavaFunctionBroker(classLoader); + FunctionEnvironmentReloadRequestHandler envHandler = new FunctionEnvironmentReloadRequestHandler(broker); + Map existingVariables = System.getenv(); + Map newEnvVariables = new HashMap<>(); + newEnvVariables.putAll(existingVariables); + newEnvVariables.put(Constants.NULLABLE_VALUES_ENABLED_APP_SETTING, "false"); + envHandler.setEnv(newEnvVariables); + Method httpRequestMessageStringBodyMethod = getFunctionMethod("HttpRequestStringBody"); + Map queryMap = new HashMap() {{ + put("name", ""); + put("count", "1"); + }}; + Map headersMap = new HashMap() {{ + put("cookie", ""); + put("accept-encoding", "gzip, deflate, br"); + }}; + Parameter[] parameters = httpRequestMessageStringBodyMethod.getParameters(); + String sourceKey = "testRpcHttp"; + RpcHttp input = getTestRpcHttp(null, headersMap, queryMap); + RpcHttpRequestDataSource rpcHttp = new RpcHttpRequestDataSource(sourceKey, input); + Optional actualBindingData = rpcHttp.computeByName(sourceKey, + parameters[0].getParameterizedType()); + BindingData actualArg = actualBindingData.orElseThrow(WrongMethodTypeException::new); + HttpRequestMessage requestMsg = (HttpRequestMessage) actualArg.getValue(); + + assertNull(requestMsg.getQueryParameters().get("name")); + assertEquals("1", requestMsg.getQueryParameters().get("count")); + assertNull(requestMsg.getHeaders().get("cookie")); + assertEquals("gzip, deflate, br", requestMsg.getHeaders().get("accept-encoding")); + } + + @Test + public void rpcHttpDataSourceToHttpRequestMessageStringBody() throws Exception { Method httpRequestMessageStringBodyMethod = getFunctionMethod("HttpRequestStringBody"); Parameter[] parameters = httpRequestMessageStringBodyMethod.getParameters(); String sourceKey = "testRpcHttp"; - RpcHttp input = getTestRpcHttp("testStringBody"); + RpcHttp input = getTestRpcHttp("testStringBody", null, null); RpcHttpRequestDataSource rpcHttp = new RpcHttpRequestDataSource(sourceKey, input); Optional actualBindingData = rpcHttp.computeByName(sourceKey, parameters[0].getParameterizedType()); @@ -58,13 +141,13 @@ public void rpcHttpDataSource_To_HttpRequestMessage_StringBody() throws Exceptio } @Test - public void rpcHttpDataSource_To_HttpRequestMessage_IntegerBody() throws Exception { + public void rpcHttpDataSourceToHttpRequestMessageIntegerBody() throws Exception { Method httpRequestMessageStringBodyMethod = getFunctionMethod("HttpRequestIntBody"); Parameter[] parameters = httpRequestMessageStringBodyMethod.getParameters(); String sourceKey = "testRpcHttp"; - RpcHttp input = getTestRpcHttp(1234); + RpcHttp input = getTestRpcHttp(1234, null, null); RpcHttpRequestDataSource rpcHttp = new RpcHttpRequestDataSource(sourceKey, input); Optional actualBindingData = rpcHttp.computeByName(sourceKey, parameters[0].getParameterizedType()); @@ -74,7 +157,7 @@ public void rpcHttpDataSource_To_HttpRequestMessage_IntegerBody() throws Excepti } @Test - public void rpcHttpDataSource_To_HttpRequestMessage_byteArrayBody() throws Exception { + public void rpcHttpDataSourceToHttpRequestMessageByteArrayBody() throws Exception { Method httpRequestMessageStringBodyMethod = getFunctionMethod("HttpRequestBinaryBody"); @@ -82,7 +165,7 @@ public void rpcHttpDataSource_To_HttpRequestMessage_byteArrayBody() throws Excep String sourceKey = "testRpcHttp"; String expectedString = "Example String"; byte[] inputBytes = expectedString.getBytes(); - RpcHttp input = getTestRpcHttp(inputBytes); + RpcHttp input = getTestRpcHttp(inputBytes, null, null); RpcHttpRequestDataSource rpcHttp = new RpcHttpRequestDataSource(sourceKey, input); Optional actualBindingData = rpcHttp.computeByName(sourceKey, parameters[0].getParameterizedType());