Muokkaa

Jaa


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.

  1. 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>
    
  2. Open local.settings.json file and locate the value named AzureWebJobsStorage, which is the Storage account connection string. You use the name AzureWebJobsStorage 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 the app.http call

    const 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

  1. 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:

    Screenshot of terminal window output when running function locally.

    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.

  2. 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 like http://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.

  3. 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:

  1. 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>"
    
  2. (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 named outqueue, which was created when the function wrote its first message to that queue.

    az storage queue list --output tsv
    
  3. 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 in outqueue, 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

  1. As in the previous quickstart, use a browser or CURL to test the redeployed function.

    Copy the complete Invoke URL shown in the output of the publish command into a browser address bar, appending the query parameter &name=Functions. The browser should display the same output as when you ran the function locally.

  2. 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: