public async Task<HttpResponseMessage> HandleRequestAsync(FunctionDescriptor function, HttpRequestMessage request, Func<HttpRequestMessage, Task<HttpResponseMessage>> invokeFunction) { // First check if there is a registered WebHook Receiver for this request, and if // so use it HttpBindingMetadata httpFunctionMetadata = (HttpBindingMetadata)function.Metadata.InputBindings.FirstOrDefault(p => p.Type == BindingType.HttpTrigger); string webHookReceiver = httpFunctionMetadata.WebHookType; IWebHookReceiver receiver = null; if (string.IsNullOrEmpty(webHookReceiver) || !_receiverLookup.TryGetValue(webHookReceiver, out receiver)) { // If the function is a not a correctly configured WebHook return 500 return new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError); } HttpRequestContext context = new HttpRequestContext { Configuration = _httpConfiguration }; request.SetConfiguration(_httpConfiguration); // add the anonymous handler function from above to the request properties // so our custom WebHookHandler can invoke it at the right time request.Properties.Add(AzureFunctionsCallbackKey, invokeFunction); // TODO: Is there a better way? Requests content can't be read multiple // times, so this forces it to buffer await request.Content.ReadAsStringAsync(); string receiverId = function.Name.ToLowerInvariant(); return await receiver.ReceiveAsync(receiverId, context, request); }
public virtual bool TryCreate(FunctionMetadata functionMetadata, out FunctionDescriptor functionDescriptor) { if (functionMetadata == null) { throw new InvalidOperationException("functionMetadata"); } functionDescriptor = null; // Default the trigger binding name if a name hasn't // been specified // TODO: Remove this logic and always require it to be explicitly // specified? foreach (var binding in functionMetadata.Bindings.Where(p => p.IsTrigger)) { if (string.IsNullOrEmpty(binding.Name)) { if (binding.Type == BindingType.HttpTrigger) { binding.Name = DefaultHttpInputParameterName; } else { binding.Name = DefaultInputParameterName; } } } // parse the bindings Collection<FunctionBinding> inputBindings = FunctionBinding.GetBindings(Config, functionMetadata.InputBindings, FileAccess.Read); Collection<FunctionBinding> outputBindings = FunctionBinding.GetBindings(Config, functionMetadata.OutputBindings, FileAccess.Write); BindingMetadata triggerMetadata = functionMetadata.InputBindings.FirstOrDefault(p => p.IsTrigger); string scriptFilePath = Path.Combine(Config.RootScriptPath, functionMetadata.Source); IFunctionInvoker invoker = null; try { invoker = CreateFunctionInvoker(scriptFilePath, triggerMetadata, functionMetadata, inputBindings, outputBindings); Collection<CustomAttributeBuilder> methodAttributes = new Collection<CustomAttributeBuilder>(); Collection<ParameterDescriptor> parameters = GetFunctionParameters(invoker, functionMetadata, triggerMetadata, methodAttributes, inputBindings, outputBindings); functionDescriptor = new FunctionDescriptor(functionMetadata.Name, invoker, functionMetadata, parameters, methodAttributes); return true; } catch (Exception) { IDisposable disposableInvoker = invoker as IDisposable; if (disposableInvoker != null) { disposableInvoker.Dispose(); } throw; } }
public async Task Generate_EndToEnd() { // construct our TimerTrigger attribute ([TimerTrigger("00:00:02", RunOnStartup = true)]) Collection<ParameterDescriptor> parameters = new Collection<ParameterDescriptor>(); ParameterDescriptor parameter = new ParameterDescriptor("timerInfo", typeof(TimerInfo)); ConstructorInfo ctorInfo = typeof(TimerTriggerAttribute).GetConstructor(new Type[] { typeof(string) }); PropertyInfo runOnStartupProperty = typeof(TimerTriggerAttribute).GetProperty("RunOnStartup"); CustomAttributeBuilder attributeBuilder = new CustomAttributeBuilder( ctorInfo, new object[] { "00:00:02" }, new PropertyInfo[] { runOnStartupProperty }, new object[] { true }); parameter.CustomAttributes.Add(attributeBuilder); parameters.Add(parameter); // create the FunctionDefinition FunctionMetadata metadata = new FunctionMetadata(); TestInvoker invoker = new TestInvoker(); FunctionDescriptor function = new FunctionDescriptor("TimerFunction", invoker, metadata, parameters); Collection<FunctionDescriptor> functions = new Collection<FunctionDescriptor>(); functions.Add(function); // Get the Type Attributes (in this case, a TimeoutAttribute) ScriptHostConfiguration scriptConfig = new ScriptHostConfiguration(); scriptConfig.FunctionTimeout = TimeSpan.FromMinutes(5); Collection<CustomAttributeBuilder> typeAttributes = ScriptHost.CreateTypeAttributes(scriptConfig); // generate the Type Type functionType = FunctionGenerator.Generate("TestScriptHost", "TestFunctions", typeAttributes, functions); // verify the generated function MethodInfo method = functionType.GetMethod("TimerFunction"); TimeoutAttribute timeoutAttribute = (TimeoutAttribute)functionType.GetCustomAttributes().Single(); Assert.Equal(TimeSpan.FromMinutes(5), timeoutAttribute.Timeout); Assert.True(timeoutAttribute.ThrowOnTimeout); Assert.True(timeoutAttribute.TimeoutWhileDebugging); ParameterInfo triggerParameter = method.GetParameters()[0]; TimerTriggerAttribute triggerAttribute = triggerParameter.GetCustomAttribute<TimerTriggerAttribute>(); Assert.NotNull(triggerAttribute); // start the JobHost which will start running the timer function JobHostConfiguration config = new JobHostConfiguration() { TypeLocator = new TypeLocator(functionType) }; config.UseTimers(); JobHost host = new JobHost(config); await host.StartAsync(); await Task.Delay(3000); await host.StopAsync(); // verify our custom invoker was called Assert.True(invoker.InvokeCount >= 2); }
public override bool TryCreate(FunctionMetadata functionMetadata, out FunctionDescriptor functionDescriptor) { functionDescriptor = null; string extension = Path.GetExtension(functionMetadata.Source).ToLower(); if (!ScriptFunctionInvoker.IsSupportedScriptType(extension)) { return false; } return base.TryCreate(functionMetadata, out functionDescriptor); }
public override bool TryCreate(FunctionMetadata functionMetadata, out FunctionDescriptor functionDescriptor) { functionDescriptor = null; string extension = Path.GetExtension(functionMetadata.Source).ToLower(); if (!(extension == ".js" || string.IsNullOrEmpty(extension))) { return false; } return base.TryCreate(functionMetadata, out functionDescriptor); }
public override bool TryCreate(FunctionMetadata functionMetadata, out FunctionDescriptor functionDescriptor) { if (functionMetadata == null) { throw new ArgumentNullException("functionMetadata"); } functionDescriptor = null; if (functionMetadata.ScriptType != ScriptType.CSharp) { return false; } return base.TryCreate(functionMetadata, out functionDescriptor); }
public override bool TryCreate(FunctionMetadata functionMetadata, out FunctionDescriptor functionDescriptor) { if (functionMetadata == null) { throw new ArgumentNullException("functionMetadata"); } functionDescriptor = null; if (!ScriptFunctionInvoker.IsSupportedScriptType(functionMetadata.ScriptType)) { return false; } return base.TryCreate(functionMetadata, out functionDescriptor); }
public override bool TryCreate(FunctionMetadata functionMetadata, out FunctionDescriptor functionDescriptor) { if (functionMetadata == null) { throw new ArgumentNullException("functionMetadata"); } functionDescriptor = null; // We can only handle script types supported by the current compilation service factory if (!_compilationServiceFactory.SupportedScriptTypes.Contains(functionMetadata.ScriptType)) { return false; } return base.TryCreate(functionMetadata, out functionDescriptor); }
public void Generate_WithMultipleOutParameters() { string functionName = "FunctionWithOuts"; Collection<ParameterDescriptor> parameters = new Collection<ParameterDescriptor>(); parameters.Add(new ParameterDescriptor("param1", typeof(string))); parameters.Add(new ParameterDescriptor("param2", typeof(string).MakeByRefType()) { Attributes = ParameterAttributes.Out }); parameters.Add(new ParameterDescriptor("param3", typeof(string).MakeByRefType()) { Attributes = ParameterAttributes.Out }); FunctionMetadata metadata = new FunctionMetadata(); TestInvoker invoker = new TestInvoker(); FunctionDescriptor function = new FunctionDescriptor(functionName, invoker, metadata, parameters); Collection<FunctionDescriptor> functions = new Collection<FunctionDescriptor>(); functions.Add(function); // Make sure we don't generate a TimeoutAttribute if FunctionTimeout is null. ScriptHostConfiguration scriptConfig = new ScriptHostConfiguration(); scriptConfig.FunctionTimeout = null; Collection<CustomAttributeBuilder> typeAttributes = ScriptHost.CreateTypeAttributes(scriptConfig); // generate the Type Type functionType = FunctionGenerator.Generate("TestScriptHost", "TestFunctions", typeAttributes, functions); // verify the generated function MethodInfo method = functionType.GetMethod(functionName); IEnumerable<Attribute> attributes = functionType.GetCustomAttributes(); Assert.Empty(attributes); ParameterInfo[] functionParams = method.GetParameters(); // Verify that we have the correct number of parameters Assert.Equal(parameters.Count, functionParams.Length); // Verify that out parameters were correctly generated Assert.True(functionParams[1].IsOut); Assert.True(functionParams[2].IsOut); // Verify that the method is invocable method.Invoke(null, new object[] { "test", null, null }); // verify our custom invoker was called Assert.Equal(1, invoker.InvokeCount); }
public virtual bool TryCreate(FunctionMetadata functionMetadata, out FunctionDescriptor functionDescriptor) { if (functionMetadata == null) { throw new InvalidOperationException("functionMetadata"); } ValidateFunction(functionMetadata); // parse the bindings Collection<FunctionBinding> inputBindings = FunctionBinding.GetBindings(Config, functionMetadata.InputBindings, FileAccess.Read); Collection<FunctionBinding> outputBindings = FunctionBinding.GetBindings(Config, functionMetadata.OutputBindings, FileAccess.Write); BindingMetadata triggerMetadata = functionMetadata.InputBindings.FirstOrDefault(p => p.IsTrigger); string scriptFilePath = Path.Combine(Config.RootScriptPath, functionMetadata.ScriptFile); functionDescriptor = null; IFunctionInvoker invoker = null; try { invoker = CreateFunctionInvoker(scriptFilePath, triggerMetadata, functionMetadata, inputBindings, outputBindings); Collection<CustomAttributeBuilder> methodAttributes = new Collection<CustomAttributeBuilder>(); Collection<ParameterDescriptor> parameters = GetFunctionParameters(invoker, functionMetadata, triggerMetadata, methodAttributes, inputBindings, outputBindings); functionDescriptor = new FunctionDescriptor(functionMetadata.Name, invoker, functionMetadata, parameters, methodAttributes); return true; } catch (Exception) { IDisposable disposableInvoker = invoker as IDisposable; if (disposableInvoker != null) { disposableInvoker.Dispose(); } throw; } }
public async Task<HttpResponseMessage> HandleRequestAsync(FunctionDescriptor function, HttpRequestMessage request, Func<HttpRequestMessage, Task<HttpResponseMessage>> invokeFunction) { // First check if there is a registered WebHook Receiver for this request, and if // so use it HttpTriggerBindingMetadata httpFunctionMetadata = (HttpTriggerBindingMetadata)function.Metadata.InputBindings.FirstOrDefault(p => string.Compare("HttpTrigger", p.Type, StringComparison.OrdinalIgnoreCase) == 0); string webHookReceiver = httpFunctionMetadata.WebHookType; IWebHookReceiver receiver = null; if (string.IsNullOrEmpty(webHookReceiver) || !_receiverLookup.TryGetValue(webHookReceiver, out receiver)) { // The function is not correctly configured. Log an error and return 500 string configurationError = string.Format(CultureInfo.InvariantCulture, "Invalid WebHook configuration. Unable to find a receiver for WebHook type '{0}'", webHookReceiver); function.Invoker.OnError(new FunctionInvocationException(configurationError)); return new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError); } HttpRequestContext context = new HttpRequestContext { Configuration = _httpConfiguration }; request.SetConfiguration(_httpConfiguration); // add the anonymous handler function from above to the request properties // so our custom WebHookHandler can invoke it at the right time request.Properties.Add(AzureFunctionsCallbackKey, invokeFunction); // Request content can't be read multiple // times, so this forces it to buffer await request.Content.LoadIntoBufferAsync(); string receiverId = function.Name.ToLowerInvariant(); // Get an optional client ID. This information is passed as the receiver ID, allowing // the receiver config to map configuration based on the client ID (primarily used for secret resolution). string clientId = GetClientID(request); string webhookId = $"{receiverId},{clientId}"; return await receiver.ReceiveAsync(webhookId, context, request); }
private static Dictionary<string, object> GetFunctionArguments(FunctionDescriptor function, HttpRequestMessage request) { ParameterDescriptor triggerParameter = function.Parameters.First(p => p.IsTrigger); Dictionary<string, object> arguments = new Dictionary<string, object>(); if (triggerParameter.Type != typeof(HttpRequestMessage)) { HttpTriggerBindingMetadata httpFunctionMetadata = (HttpTriggerBindingMetadata)function.Metadata.InputBindings.FirstOrDefault(p => string.Compare("HttpTrigger", p.Type, StringComparison.OrdinalIgnoreCase) == 0); if (!string.IsNullOrEmpty(httpFunctionMetadata.WebHookType)) { WebHookHandlerContext webHookContext; if (request.Properties.TryGetValue(ScriptConstants.AzureFunctionsWebHookContextKey, out webHookContext)) { // For WebHooks we want to use the WebHook library conversion methods // Stuff the resolved data into the request context so the HttpTrigger binding // can access it var webHookData = GetWebHookData(triggerParameter.Type, webHookContext); request.Properties.Add(ScriptConstants.AzureFunctionsWebHookDataKey, webHookData); } } // see if the function defines a parameter to receive the HttpRequestMessage and // if so, pass it along ParameterDescriptor requestParameter = function.Parameters.FirstOrDefault(p => p.Type == typeof(HttpRequestMessage)); if (requestParameter != null) { arguments.Add(requestParameter.Name, request); } } arguments.Add(triggerParameter.Name, request); return arguments; }
public async Task<HttpResponseMessage> HandleRequestAsync(FunctionDescriptor function, HttpRequestMessage request, CancellationToken cancellationToken) { // All authentication is assumed to have been done on the request // BEFORE this method is called Dictionary<string, object> arguments = GetFunctionArguments(function, request); // Suspend the current synchronization context so we don't pass the ASP.NET // context down to the function. using (var syncContextSuspensionScope = new SuspendedSynchronizationContextScope()) { await Instance.CallAsync(function.Name, arguments, cancellationToken); } // Get the response HttpResponseMessage response = null; if (!request.Properties.TryGetValue<HttpResponseMessage>(ScriptConstants.AzureFunctionsHttpResponseKey, out response)) { // the function was successful but did not write an explicit response response = new HttpResponseMessage(HttpStatusCode.OK); } return response; }
public virtual bool TryCreate(FunctionMetadata functionMetadata, out FunctionDescriptor functionDescriptor) { functionDescriptor = null; if (functionMetadata.IsDisabled) { return false; } // parse the bindings Collection<FunctionBinding> inputBindings = FunctionBinding.GetBindings(Config, functionMetadata.InputBindings, FileAccess.Read); Collection<FunctionBinding> outputBindings = FunctionBinding.GetBindings(Config, functionMetadata.OutputBindings, FileAccess.Write); BindingMetadata triggerMetadata = functionMetadata.InputBindings.FirstOrDefault(p => p.IsTrigger); BindingType triggerType = triggerMetadata.Type; string triggerParameterName = triggerMetadata.Name; bool triggerNameSpecified = true; if (string.IsNullOrEmpty(triggerParameterName)) { // default the name to simply 'input' triggerMetadata.Name = triggerParameterName = "input"; triggerNameSpecified = false; } Collection<CustomAttributeBuilder> methodAttributes = new Collection<CustomAttributeBuilder>(); ParameterDescriptor triggerParameter = null; bool omitInputParameter = false; switch (triggerType) { case BindingType.QueueTrigger: triggerParameter = ParseQueueTrigger((QueueBindingMetadata)triggerMetadata); break; case BindingType.BlobTrigger: triggerParameter = ParseBlobTrigger((BlobBindingMetadata)triggerMetadata); break; case BindingType.ServiceBusTrigger: triggerParameter = ParseServiceBusTrigger((ServiceBusBindingMetadata)triggerMetadata); break; case BindingType.TimerTrigger: omitInputParameter = true; triggerParameter = ParseTimerTrigger((TimerBindingMetadata)triggerMetadata, typeof(TimerInfo)); break; case BindingType.HttpTrigger: if (!triggerNameSpecified) { triggerMetadata.Name = triggerParameterName = "req"; } triggerParameter = ParseHttpTrigger((HttpBindingMetadata)triggerMetadata, methodAttributes, typeof(HttpRequestMessage)); break; case BindingType.ManualTrigger: triggerParameter = ParseManualTrigger(triggerMetadata, methodAttributes); break; } Collection<ParameterDescriptor> parameters = new Collection<ParameterDescriptor>(); triggerParameter.IsTrigger = true; parameters.Add(triggerParameter); // Add a TraceWriter for logging parameters.Add(new ParameterDescriptor("log", typeof(TraceWriter))); // Add an IBinder to support the binding programming model parameters.Add(new ParameterDescriptor("binder", typeof(IBinder))); // Add ExecutionContext to provide access to InvocationId, etc. parameters.Add(new ParameterDescriptor("context", typeof(ExecutionContext))); string scriptFilePath = Path.Combine(Config.RootScriptPath, functionMetadata.Source); IFunctionInvoker invoker = CreateFunctionInvoker(scriptFilePath, triggerMetadata, functionMetadata, omitInputParameter, inputBindings, outputBindings); functionDescriptor = new FunctionDescriptor(functionMetadata.Name, invoker, functionMetadata, parameters, methodAttributes); return true; }