Connect Azure Functions to Azure Storage using command line tools
In this article, you integrate an Azure Storage queue with the function and storage account you created in the previous quickstart article. You achieve this integration by using an output binding that writes data from an HTTP request to a message in the queue. Completing this article incurs no additional costs beyond the few USD cents of the previous quickstart. To learn more about bindings, see Azure Functions triggers and bindings concepts.
Configure your local environment
Before you begin, you must complete the article, Quickstart: Create an Azure Functions project from the command line. If you already cleaned up resources at the end of that article, go through the steps again to recreate the function app and related resources in Azure.
Before you begin, you must complete the article, Quickstart: Create an Azure Functions project from the command line. If you already cleaned up resources at the end of that article, go through the steps again to recreate the function app and related resources in Azure.
Before you begin, you must complete the article, Quickstart: Create an Azure Functions project from the command line. If you already cleaned up resources at the end of that article, go through the steps again to recreate the function app and related resources in Azure.
Before you begin, you must complete the article, Quickstart: Create an Azure Functions project from the command line. If you already cleaned up resources at the end of that article, go through the steps again to recreate the function app and related resources in Azure.
Before you begin, you must complete the article, Quickstart: Create an Azure Functions project from the command line. If you already cleaned up resources at the end of that article, go through the steps again to recreate the function app and related resources in Azure.
Before you begin, you must complete the article, Quickstart: Create an Azure Functions project from the command line. If you already cleaned up resources at the end of that article, go through the steps again to recreate the function app and related resources in Azure.
Retrieve the Azure Storage connection string
Earlier, you created an Azure Storage account for function app's use. The connection string for this account is stored securely in app settings in Azure. By downloading the setting into the local.settings.json file, you can use the connection to write to a Storage queue in the same account when running the function locally.
From the root of the project, run the following command, replace
<APP_NAME>
with the name of your function app from the previous step. This command overwrites any existing values in the file.func azure functionapp fetch-app-settings <APP_NAME>
Open local.settings.json file and locate the value named
AzureWebJobsStorage
, which is the Storage account connection string. You use the nameAzureWebJobsStorage
and the connection string in other sections of this article.
Important
Because the local.settings.json file contains secrets downloaded from Azure, always exclude this file from source control. The .gitignore file created with a local functions project excludes the file by default.
Register binding extensions
Except for HTTP and timer triggers, bindings are implemented as extension packages. Run the following dotnet add package command in the Terminal window to add the Storage extension package to your project.
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues --prerelease
Now, you can add the storage output binding to your project.
Add an output binding definition to the function
Although a function can have only one trigger, it can have multiple input and output bindings, which lets you connect to other Azure services and resources without writing custom integration code.
When using the Node.js v4 programming model, binding attributes are defined directly in the ./src/functions/HttpExample.js file. From the previous quickstart, your file already contains an HTTP binding defined by the app.http
method.
const { app } = require('@azure/functions');
app.http('httpTrigger', {
methods: ['GET', 'POST'],
authLevel: 'anonymous',
handler: async (request, context) => {
try {
context.log(`Http function processed request for url "${request.url}"`);
const name = request.query.get('name') || (await request.text());
context.log(`Name: ${name}`);
if (!name) {
return { status: 404, body: 'Not Found' };
}
return { body: `Hello, ${name}!` };
} catch (error) {
context.log(`Error: ${error}`);
return { status: 500, body: 'Internal Server Error' };
}
},
});
When using the Node.js v4 programming model, binding attributes are defined directly in the ./src/functions/HttpExample.js file. From the previous quickstart, your file already contains an HTTP binding defined by the app.http
method.
import {
app,
HttpRequest,
HttpResponseInit,
InvocationContext,
} from '@azure/functions';
export async function httpTrigger1(
request: HttpRequest,
context: InvocationContext,
): Promise<HttpResponseInit> {
context.log(`Http function processed request for url "${request.url}"`);
const name = request.query.get('name') || (await request.text()) || 'world';
return { body: `Hello, ${name}!` };
}
app.http('httpTrigger1', {
methods: ['GET', 'POST'],
authLevel: 'anonymous',
handler: httpTrigger1,
});
You declare these bindings in the function.json file in your function folder. From the previous quickstart, your function.json file in the HttpExample folder contains two bindings in the bindings
collection:
When using the Python v2 programming model, binding attributes are defined directly in the function_app.py file as decorators. From the previous quickstart, your function_app.py file already contains one decorator-based binding:
import azure.functions as func
import logging
app = func.FunctionApp()
@app.function_name(name="HttpTrigger1")
@app.route(route="hello", auth_level=func.AuthLevel.ANONYMOUS)
The route
decorator adds HttpTrigger and HttpOutput binding to the function, which enables your function be triggered when http requests hit the specified route.
To write to an Azure Storage queue from this function, add the queue_output
decorator to your function code:
@app.queue_output(arg_name="msg", queue_name="outqueue", connection="AzureWebJobsStorage")
In the decorator, arg_name
identifies the binding parameter referenced in your code, queue_name
is name of the queue that the binding writes to, and connection
is the name of an application setting that contains the connection string for the Storage account. In quickstarts you use the same storage account as the function app, which is in the AzureWebJobsStorage
setting (from local.settings.json file). When the queue_name
doesn't exist, the binding creates it on first use.
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "Request",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "Response"
}
]
To write to an Azure Storage queue:
Add an
extraOutputs
property to the binding configuration{ methods: ['GET', 'POST'], extraOutputs: [sendToQueue], // add output binding to HTTP trigger authLevel: 'anonymous', handler: () => {} }
Add a
output.storageQueue
function above theapp.http
callconst sendToQueue: StorageQueueOutput = output.storageQueue({ queueName: 'outqueue', connection: 'AzureWebJobsStorage', });
The second binding in the collection is named res
. This http
binding is an output binding (out
) that is used to write the HTTP response.
To write to an Azure Storage queue from this function, add an out
binding of type queue
with the name msg
, as shown in the code below:
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "Request",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "Response"
},
{
"type": "queue",
"direction": "out",
"name": "msg",
"queueName": "outqueue",
"connection": "AzureWebJobsStorage"
}
]
}
For a queue
type, you must specify the name of the queue in queueName
and provide the name of the Azure Storage connection (from local.settings.json file) in connection
.
In a C# project, the bindings are defined as binding attributes on the function method. Specific definitions depend on whether your app runs in-process (C# class library) or in an isolated worker process.
Open the HttpExample.cs project file and add the following MultiResponse
class:
public class MultiResponse
{
[QueueOutput("outqueue",Connection = "AzureWebJobsStorage")]
public string[] Messages { get; set; }
public HttpResponseData HttpResponse { get; set; }
}
The MultiResponse
class allows you to write to a storage queue named outqueue
and an HTTP success message. Multiple messages could be sent to the queue because the QueueOutput
attribute is applied to a string array.
The Connection
property sets the connection string for the storage account. In this case, you could omit Connection
because you're already using the default storage account.
In a Java project, the bindings are defined as binding annotations on the function method. The function.json file is then autogenerated based on these annotations.
Browse to the location of your function code under src/main/java, open the Function.java project file, and add the following parameter to the run
method definition:
@QueueOutput(name = "msg", queueName = "outqueue", connection = "AzureWebJobsStorage") OutputBinding<String> msg
The msg
parameter is an OutputBinding<T>
type, which represents a collection of strings. These strings are written as messages to an output binding when the function completes. In this case, the output is a storage queue named outqueue
. The connection string for the Storage account is set by the connection
method. You pass the application setting that contains the Storage account connection string, rather than passing the connection string itself.
The run
method definition must now look like the following example:
@FunctionName("HttpTrigger-Java")
public HttpResponseMessage run(
@HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.FUNCTION)
HttpRequestMessage<Optional<String>> request,
@QueueOutput(name = "msg", queueName = "outqueue", connection = "AzureWebJobsStorage")
OutputBinding<String> msg, final ExecutionContext context) {
...
}
For more information on the details of bindings, see Azure Functions triggers and bindings concepts and queue output configuration.
Add code to use the output binding
With the queue binding defined, you can now update your function to receive the msg
output parameter and write messages to the queue.
Update HttpExample\function_app.py to match the following code, add the msg
parameter to the function definition and msg.set(name)
under the if name:
statement:
import azure.functions as func
import logging
app = func.FunctionApp(http_auth_level=func.AuthLevel.ANONYMOUS)
@app.route(route="HttpExample")
@app.queue_output(arg_name="msg", queue_name="outqueue", connection="AzureWebJobsStorage")
def HttpExample(req: func.HttpRequest, msg: func.Out [func.QueueMessage]) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
name = req.params.get('name')
if not name:
try:
req_body = req.get_json()
except ValueError:
pass
else:
name = req_body.get('name')
if name:
msg.set(name)
return func.HttpResponse(f"Hello, {name}. This HTTP triggered function executed successfully.")
else:
return func.HttpResponse(
"This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.",
status_code=200
)
The msg
parameter is an instance of the azure.functions.Out class
. The set
method writes a string message to the queue. In this case, it's the name
passed to the function in the URL query string.
Add code that uses the output binding object on context.extraOutputs
to create a queue message. Add this code before the return statement.
context.extraOutputs.set(sendToQueue, [msg]);
At this point, your function could look as follows:
const { app, output } = require('@azure/functions');
const sendToQueue = output.storageQueue({
queueName: 'outqueue',
connection: 'AzureWebJobsStorage',
});
app.http('HttpExample', {
methods: ['GET', 'POST'],
authLevel: 'anonymous',
extraOutputs: [sendToQueue],
handler: async (request, context) => {
try {
context.log(`Http function processed request for url "${request.url}"`);
const name = request.query.get('name') || (await request.text());
context.log(`Name: ${name}`);
if (name) {
const msg = `Name passed to the function ${name}`;
context.extraOutputs.set(sendToQueue, [msg]);
return { body: msg };
} else {
context.log('Missing required data');
return { status: 404, body: 'Missing required data' };
}
} catch (error) {
context.log(`Error: ${error}`);
return { status: 500, body: 'Internal Server Error' };
}
},
});
Add code that uses the output binding object on context.extraOutputs
to create a queue message. Add this code before the return statement.
context.extraOutputs.set(sendToQueue, [msg]);
At this point, your function could look as follows:
import {
app,
output,
HttpRequest,
HttpResponseInit,
InvocationContext,
StorageQueueOutput,
} from '@azure/functions';
const sendToQueue: StorageQueueOutput = output.storageQueue({
queueName: 'outqueue',
connection: 'AzureWebJobsStorage',
});
export async function HttpExample(
request: HttpRequest,
context: InvocationContext,
): Promise<HttpResponseInit> {
try {
context.log(`Http function processed request for url "${request.url}"`);
const name = request.query.get('name') || (await request.text());
context.log(`Name: ${name}`);
if (name) {
const msg = `Name passed to the function ${name}`;
context.extraOutputs.set(sendToQueue, [msg]);
return { body: msg };
} else {
context.log('Missing required data');
return { status: 404, body: 'Missing required data' };
}
} catch (error) {
context.log(`Error: ${error}`);
return { status: 500, body: 'Internal Server Error' };
}
}
app.http('HttpExample', {
methods: ['GET', 'POST'],
authLevel: 'anonymous',
handler: HttpExample,
});
Add code that uses the Push-OutputBinding
cmdlet to write text to the queue using the msg
output binding. Add this code before you set the OK status in the if
statement.
$outputMsg = $name
Push-OutputBinding -name msg -Value $outputMsg
At this point, your function must look as follows:
using namespace System.Net
# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)
# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."
# Interact with query parameters or the body of the request.
$name = $Request.Query.Name
if (-not $name) {
$name = $Request.Body.Name
}
if ($name) {
# Write the $name value to the queue,
# which is the name passed to the function.
$outputMsg = $name
Push-OutputBinding -name msg -Value $outputMsg
$status = [HttpStatusCode]::OK
$body = "Hello $name"
}
else {
$status = [HttpStatusCode]::BadRequest
$body = "Please pass a name on the query string or in the request body."
}
# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = $status
Body = $body
})
Replace the existing HttpExample
class with the following code:
[Function("HttpExample")]
public static MultiResponse Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req,
FunctionContext executionContext)
{
var logger = executionContext.GetLogger("HttpExample");
logger.LogInformation("C# HTTP trigger function processed a request.");
var message = "Welcome to Azure Functions!";
var response = req.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
response.WriteString(message);
// Return a response to both HTTP trigger and storage output binding.
return new MultiResponse()
{
// Write a single message.
Messages = new string[] { message },
HttpResponse = response
};
}
}
Now, you can use the new msg
parameter to write to the output binding from your function code. Add the following line of code before the success response to add the value of name
to the msg
output binding.
msg.setValue(name);
When you use an output binding, you don't have to use the Azure Storage SDK code for authentication, getting a queue reference, or writing data. The Functions runtime and queue output binding do those tasks for you.
Your run
method must now look like the following example:
public HttpResponseMessage run(
@HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS)
HttpRequestMessage<Optional<String>> request,
@QueueOutput(name = "msg", queueName = "outqueue",
connection = "AzureWebJobsStorage") OutputBinding<String> msg,
final ExecutionContext context) {
context.getLogger().info("Java HTTP trigger processed a request.");
// Parse query parameter
String query = request.getQueryParameters().get("name");
String name = request.getBody().orElse(query);
if (name == null) {
return request.createResponseBuilder(HttpStatus.BAD_REQUEST)
.body("Please pass a name on the query string or in the request body").build();
} else {
// Write the name to the message queue.
msg.setValue(name);
return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + name).build();
}
}
Update the tests
Because the archetype also creates a set of tests, you need to update these tests to handle the new msg
parameter in the run
method signature.
Browse to the location of your test code under src/test/java, open the Function.java project file, and replace the line of code under //Invoke
with the following code:
@SuppressWarnings("unchecked")
final OutputBinding<String> msg = (OutputBinding<String>)mock(OutputBinding.class);
final HttpResponseMessage ret = new Function().run(req, msg, context);
Observe that you don't need to write any code for authentication, getting a queue reference, or writing data. All these integration tasks are conveniently handled in the Azure Functions runtime and queue output binding.
Run the function locally
Run your function by starting the local Azure Functions runtime host from the LocalFunctionProj folder.
func start
Toward the end of the output, the following lines must appear:
Note
If HttpExample doesn't appear as shown above, you likely started the host from outside the root folder of the project. In that case, use Ctrl+C to stop the host, go to the project's root folder, and run the previous command again.
Copy the URL of your HTTP function from this output to a browser and append the query string
?name=<YOUR_NAME>
, making the full URL likehttp://localhost:7071/api/HttpExample?name=Functions
. The browser should display a response message that echoes back your query string value. The terminal in which you started your project also shows log output as you make requests.When you're done, press Ctrl + C and type
y
to stop the functions host.
Tip
During startup, the host downloads and installs the Storage binding extension and other Microsoft binding extensions. This installation happens because binding extensions are enabled by default in the host.json file with the following properties:
{
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[1.*, 2.0.0)"
}
}
If you encounter any errors related to binding extensions, check that the above properties are present in host.json.
View the message in the Azure Storage queue
You can view the queue in the Azure portal or in the Microsoft Azure Storage Explorer. You can also view the queue in the Azure CLI, as described in the following steps:
Open the function project's local.setting.json file and copy the connection string value. In a terminal or command window, run the following command to create an environment variable named
AZURE_STORAGE_CONNECTION_STRING
, and paste your specific connection string in place of<MY_CONNECTION_STRING>
. (This environment variable means you don't need to supply the connection string to each subsequent command using the--connection-string
argument.)export AZURE_STORAGE_CONNECTION_STRING="<MY_CONNECTION_STRING>"
(Optional) Use the
az storage queue list
command to view the Storage queues in your account. The output from this command must include a queue namedoutqueue
, which was created when the function wrote its first message to that queue.az storage queue list --output tsv
Use the
az storage message get
command to read the message from this queue, which should be the value you supplied when testing the function earlier. The command reads and removes the first message from the queue.echo `echo $(az storage message get --queue-name outqueue -o tsv --query '[].{Message:content}') | base64 --decode`
Because the message body is stored base64 encoded, the message must be decoded before it's displayed. After you execute
az storage message get
, the message is removed from the queue. If there was only one message inoutqueue
, you won't retrieve a message when you run this command a second time and instead get an error.
Redeploy the project to Azure
Now that you've verified locally that the function wrote a message to the Azure Storage queue, you can redeploy your project to update the endpoint running on Azure.
In the LocalFunctionsProj folder, use the func azure functionapp publish
command to redeploy the project, replacing<APP_NAME>
with the name of your app.
func azure functionapp publish <APP_NAME>
In the local project folder, use the following Maven command to republish your project:
mvn azure-functions:deploy
Verify in Azure
As in the previous quickstart, use a browser or CURL to test the redeployed function.
Examine the Storage queue again, as described in the previous section, to verify that it contains the new message written to the queue.
Clean up resources
After you've finished, use the following command to delete the resource group and all its contained resources to avoid incurring further costs.
az group delete --name AzureFunctionsQuickstart-rg
Next steps
You've updated your HTTP triggered function to write data to a Storage queue. Now you can learn more about developing Functions from the command line using Core Tools and Azure CLI: