Conexión de Azure Functions a Azure Storage mediante herramientas de línea de comandos

En este artículo, integrará una cola de Azure Storage con la función y la cuenta de almacenamiento que creó en el artículo de inicio rápido anterior. Para lograr esta integración se usa un enlace de salida que escribe los datos de una solicitud HTTP en un mensaje de la cola. Completar este artículo no supone ningún costo adicional sobre el pequeño importe del inicio rápido anterior. Para más información acerca de los enlaces, consulte Conceptos básicos sobre los enlaces y desencadenadores de Azure Functions.

Configuración del entorno local

Antes de empezar, debe completar el artículo Inicio rápido: Creación de un proyecto de Azure Functions desde la línea de comandos. Si ya ha limpiado los recursos al final de ese artículo, vuelva a recorrer los pasos para crear de nuevo la aplicación de función y los recursos relacionados en Azure.

Antes de empezar, debe completar el artículo Inicio rápido: Creación de un proyecto de Azure Functions desde la línea de comandos. Si ya ha limpiado los recursos al final de ese artículo, vuelva a recorrer los pasos para crear de nuevo la aplicación de función y los recursos relacionados en Azure.

Antes de empezar, debe completar el artículo Inicio rápido: Creación de un proyecto de Azure Functions desde la línea de comandos. Si ya ha limpiado los recursos al final de ese artículo, vuelva a recorrer los pasos para crear de nuevo la aplicación de función y los recursos relacionados en Azure.

Antes de empezar, debe completar el artículo Inicio rápido: Creación de un proyecto de Azure Functions desde la línea de comandos. Si ya ha limpiado los recursos al final de ese artículo, vuelva a recorrer los pasos para crear de nuevo la aplicación de función y los recursos relacionados en Azure.

Antes de empezar, debe completar el artículo Inicio rápido: Creación de un proyecto de Azure Functions desde la línea de comandos. Si ya ha limpiado los recursos al final de ese artículo, vuelva a recorrer los pasos para crear de nuevo la aplicación de función y los recursos relacionados en Azure.

Antes de empezar, debe completar el artículo Inicio rápido: Creación de un proyecto de Azure Functions desde la línea de comandos. Si ya ha limpiado los recursos al final de ese artículo, vuelva a recorrer los pasos para crear de nuevo la aplicación de función y los recursos relacionados en Azure.

Recuperación de la cadena de conexión de Azure Storage

Anteriormente, creó una cuenta de Azure Storage para el uso de la aplicación de funciones. La cadena de conexión de esta cuenta se almacena de forma segura en la configuración de la aplicación en Azure. Mediante la descarga de la configuración en el archivo local.settings.json, puede usar la conexión para escribir en una cola de Storage de la misma cuenta cuando ejecute la función de forma local.

  1. En la raíz del proyecto, ejecute el comando siguiente, reemplace <APP_NAME> por el nombre de la aplicación de funciones del paso anterior. Este comando sobrescribe los valores existentes en el archivo.

    func azure functionapp fetch-app-settings <APP_NAME>
    
  2. Abra el archivo local.settings.json y busque el valor denominado AzureWebJobsStorage, que es la cadena de conexión de la cuenta de almacenamiento. Usará el nombre AzureWebJobsStorage y la cadena de conexión en otras secciones de este artículo.

Importante

Como el archivo local.settings.json contiene secretos descargados de Azure, excluya siempre este archivo del control de código fuente. El archivo .gitignore que se creó con un proyecto de Functions local excluye el archivo de forma predeterminada.

Registro de extensiones de enlace

Excepto los desencadenadores HTTP y del temporizador, los enlaces se implementan como paquetes de extensión. Ejecute el siguiente comando dotnet add package en la ventana Terminal para agregar el paquete de extensión de Storage al proyecto.

dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues --prerelease

Ahora podrá agregar el enlace de salida de almacenamiento al proyecto.

Incorporación de una definición de enlace de salida a la función

Aunque ninguna de las funciones puede tener más de un desencadenador, puede tener varios enlaces de entrada y salida que le permiten conectarse a otros servicios y recursos de Azure sin escribir código de integración personalizado.

Al usar el modelo de programación de Node.js v4, los atributos de enlace se definen directamente en el archivo ./src/functions/HttpExample.js. En el inicio rápido anterior, el archivo ya contiene un enlace HTTP definido por el app.http método.

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' };
    }
  },
});

Al usar el modelo de programación de Node.js v4, los atributos de enlace se definen directamente en el archivo ./src/functions/HttpExample.js. En el inicio rápido anterior, el archivo ya contiene un enlace HTTP definido por el app.http método.

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,
});

Estos enlaces se declaran en el archivo function.json en la carpeta de la función. En el inicio rápido anterior, el archivo function.json de la carpeta HttpExample contiene dos enlaces en la colección bindings:

Si se usa el modelo de programación de Python v2, los atributos de enlace se definen directamente en el archivo function_app.py como decoradores. En el inicio rápido anterior, el archivo function_app.py ya contiene un enlace basado en decorador:

import azure.functions as func
import logging

app = func.FunctionApp()

@app.function_name(name="HttpTrigger1")
@app.route(route="hello", auth_level=func.AuthLevel.ANONYMOUS)

El decorador route agrega el enlace HttpTrigger y HttpOutput a la función, lo que permite que la función se desencadene cuando las solicitudes http alcancen la ruta especificada.

Para escribir en una cola de Azure Storage desde esta función, agregue el decorador queue_output al código de función:

@app.queue_output(arg_name="msg", queue_name="outqueue", connection="AzureWebJobsStorage")

En el decorador, arg_name identifica el parámetro de enlace al que se hace referencia en el código, queue_name es el nombre de la cola en la que escribe el enlace y connection es el nombre de una configuración de la aplicación que contiene la cadena de conexión para la cuenta de Storage. En los inicios rápidos se usa la misma cuenta de almacenamiento que la aplicación de funciones, que se encuentra en la configuración AzureWebJobsStorage (desde el archivo local.settings.json). Cuando no existe queue_name, el enlace lo crea durante el primer uso.

"bindings": [
  {
    "authLevel": "function",
    "type": "httpTrigger",
    "direction": "in",
    "name": "Request",
    "methods": [
      "get",
      "post"
    ]
  },
  {
    "type": "http",
    "direction": "out",
    "name": "Response"
  }
]

Para escribir en una cola de Azure Storage:

  • Agregar una propiedad extraOutputs a la configuración de enlace

    {
        methods: ['GET', 'POST'],
        extraOutputs: [sendToQueue], // add output binding to HTTP trigger
        authLevel: 'anonymous',
        handler: () => {}
    }
    
  • Agregar una función output.storageQueue encima de la llamada app.http

    const sendToQueue: StorageQueueOutput = output.storageQueue({
      queueName: 'outqueue',
      connection: 'AzureWebJobsStorage',
    });
    

El segundo enlace de la colección se denomina res. Este enlace http es un enlace de salida (out) que se usa para escribir la respuesta HTTP.

Para escribir en una cola de Azure Storage desde esta función, agregue un enlace out del tipo queue con el nombre msg, como se muestra en el código siguiente:

    {
      "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"
    }
  ]
}

En el caso de un tipo queue, también debe especificar el nombre de la cola en queueName y proporcionar el nombre de la conexión Azure Storage (desde el archivo local.settings.json) en connection.

En un proyecto de C#, los enlaces se definen como atributos de enlace en el método de función. Las definiciones específicas dependen de si la aplicación se ejecuta en proceso (biblioteca de clases de C#) o en un proceso de trabajo aislado.

Abra el archivo de proyecto HttpExample.cs y agregue la siguiente clase MultiResponse:

public class MultiResponse
{
    [QueueOutput("outqueue",Connection = "AzureWebJobsStorage")]
    public string[] Messages { get; set; }
    public HttpResponseData HttpResponse { get; set; }
}

La clase MultiResponse le permite escribir en una cola de almacenamiento denominada outqueue y en un mensaje HTTP de operación correcta. Como el atributo QueueOutput se aplica a una matriz de cadenas, se podrían enviar varios mensajes a la cola.

La propiedad Connection le permite establecer una cadena de conexión para la cuenta de almacenamiento. En este caso, puede omitir Connection, puesto que ya está usando la cuenta de almacenamiento predeterminada.

En un proyecto de Java, los enlaces se definen como anotaciones de enlace en el método de función. Entonces, el archivo function.json se genera automáticamente en función de estas anotaciones.

Vaya a la ubicación del código de función en src/main/java, abra el archivo de proyecto Function.java y agregue el parámetro siguiente a la definición del método run:

@QueueOutput(name = "msg", queueName = "outqueue", connection = "AzureWebJobsStorage") OutputBinding<String> msg

El parámetro msg es un tipo OutputBinding<T>, que representa una colección de cadenas. Estas cadenas se escriben como mensajes en un enlace de salida cuando se completa la función. En este caso, la salida es una cola de almacenamiento denominada outqueue. La cadena de conexión de la cuenta de Storage la establece el método connection. Se pasa la configuración de la aplicación que contiene la cadena de conexión de la cuenta Storage, en lugar de pasar la propia cadena de conexión.

La definición del método run debe ahora parecerse a la del siguiente ejemplo:

@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) {
    ...
}

Para más información sobre los enlaces, consulte Conceptos básicos sobre los enlaces y desencadenadores de Azure Functions y configuración de la cola de salida.

Adición de código para usar el enlace de salida

Con el enlace de cola definido, ahora puede actualizar la función para que reciba el parámetro de salida msg y escribir mensajes en la cola.

Actualice HttpExample\function_app.py para que se ajuste al siguiente código y agregue el parámetro msg a la definición de la función y msg.set(name) en la instrucción if name::

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
        )

El parámetro msg es una instancia de azure.functions.Out class. El método set escribe un mensaje de cadena en la cola. En este caso, es el name que pasa a la función en la cadena de consulta de URL.

Agregue código que use el objeto de enlace de salida en context.extraOutputs para crear un mensaje de cola. Agregue este código antes de la instrucción return.

context.extraOutputs.set(sendToQueue, [msg]);

En este momento, la función podría tener el aspecto siguiente:

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' };
    }
  },
});

Agregue código que use el objeto de enlace de salida en context.extraOutputs para crear un mensaje de cola. Agregue este código antes de la instrucción return.

context.extraOutputs.set(sendToQueue, [msg]);

En este momento, la función podría tener el aspecto siguiente:

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,
});

Agregue código que use el cmdlet Push-OutputBinding para escribir texto en la cola mediante el enlace de salida msg. Agregue este código antes de establecer el estado correcto en la instrucción if.

$outputMsg = $name
Push-OutputBinding -name msg -Value $outputMsg

En este momento, la función debe tener el aspecto siguiente:

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
})

Reemplace la clase HttpExample existente por el código siguiente:

    [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
        };
    }
}

Ahora, puede usar el nuevo parámetro msg para escribir en el enlace de salida desde el código de la función. Agregue la siguiente línea de código antes de la respuesta de operación correcta para agregar el valor de name al enlace de salida msg.

msg.setValue(name);

Al usar un enlace de salida, no tiene que usar el código del SDK de Azure Storage para autenticarse, obtener una referencia de cola o escribir datos. El sistema en tiempo de ejecución de Functions y el enlace de salida de cola realizan esas tareas automáticamente.

El método run debe ahora parecerse al del siguiente ejemplo:

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();
    }
}

Actualización de las pruebas

Dado que el arquetipo también crea un conjunto de pruebas, debe actualizar estas pruebas para controlar el nuevo parámetro msg en la signatura del método run.

Vaya a la ubicación del código de prueba en src/test/java, abra el archivo de proyecto Function.Java y reemplace la línea de código debajo de //Invoke por el código siguiente:

@SuppressWarnings("unchecked")
final OutputBinding<String> msg = (OutputBinding<String>)mock(OutputBinding.class);
final HttpResponseMessage ret = new Function().run(req, msg, context);

Observe que no necesita escribir código para la autenticación, para obtener una referencia de la cola ni para escribir datos. Todas estas tareas de integración se administran de manera adecuada en el entorno de ejecución de Azure Functions y en el enlace de salida de la cola.

Ejecución local de la función

  1. Para ejecutar la función, inicie el host en tiempo de ejecución local de Azure Functions desde la carpeta LocalFunctionProj.

    func start
    

    En la parte final de la salida, deberán aparecer las líneas siguientes:

    Captura de pantalla de la salida de la ventana de terminal al ejecutar la función localmente.

    Nota

    Si HttpExample no aparece como se ha mostrado arriba, es probable que haya iniciado el host desde fuera de la carpeta raíz del proyecto. En ese caso, use Ctrl+C para detener el host, vaya a la carpeta raíz del proyecto y vuelva a ejecutar el comando anterior.

  2. Copie la URL de su función HTTP de esta salida a un navegador y agregue la cadena de consulta ?name=<YOUR_NAME>, lo que hace que la URL completa sea como http://localhost:7071/api/HttpExample?name=Functions. El explorador debe mostrar un mensaje de respuesta que devuelve el valor de la cadena de consulta. El terminal en el que inició el proyecto también muestra la salida del registro cuando realiza solicitudes.

  3. Cuando termine, presione Ctrl + C y escriba y para detener el host de Azure Functions.

Sugerencia

Durante el inicio, el host descarga e instala la extensión de enlace de Storage y otras extensiones de enlace de Microsoft. Esta instalación se produce porque las extensiones de enlace se habilitan de forma predeterminada en el archivo host.json con las siguientes propiedades:

{
    "version": "2.0",
    "extensionBundle": {
        "id": "Microsoft.Azure.Functions.ExtensionBundle",
        "version": "[1.*, 2.0.0)"
    }
}

Si encuentra algún error relacionado con las extensiones de enlace, compruebe que las propiedades anteriores están presentes en host.json.

Visualización del mensaje en la cola de Azure Storage

La cola se puede ver en Azure Portal o en el Explorador de Microsoft Azure Storage. También puede ver la cola en la CLI de Azure, como se describe en los pasos siguientes:

  1. Abra el archivo local.setting.json del proyecto de Functions y copie el valor de la cadena de conexión. En una ventana de terminal o de comandos, ejecute el siguiente comando para crear una variable de entorno denominada AZURE_STORAGE_CONNECTION_STRING y pegue la cadena de conexión concreta en lugar de <MY_CONNECTION_STRING>. (Esta variable de entorno significa que no es necesario proporcionar la cadena de conexión a cada comando posterior mediante el argumento --connection-string).

    export AZURE_STORAGE_CONNECTION_STRING="<MY_CONNECTION_STRING>"
    
  2. (Opcional) Puede usar el comando az storage queue list para ver las colas de Storage de la cuenta. La salida de este comando debe incluir una cola denominada outqueue, la cual se creó cuando la función escribió su primer mensaje en esa cola.

    az storage queue list --output tsv
    
  3. Use el comando az storage message get para leer el mensaje de esta cola, que debería ser el valor que indicó al probar la función anteriormente. El comando lee y quita el primer mensaje de la cola.

    echo `echo $(az storage message get --queue-name outqueue -o tsv --query '[].{Message:content}') | base64 --decode`
    

    Dado que el cuerpo del mensaje se almacena codificado en Base64, el mensaje se debe descodificar antes de visualizarse. Después de ejecutar az storage message get, el mensaje se quita de la cola. Si solo había un mensaje en outqueue, no se recuperará al ejecutar este comando una segunda vez y, en su lugar, recibirá un error.

Nueva implementación del proyecto en Azure

Ahora que ha comprobado localmente que la función escribió un mensaje en la cola de Azure Storage, puede volver a implementar el proyecto para actualizar el punto de conexión que se ejecuta en Azure.

En la carpeta LocalFunctionsProj, use el comando func azure functionapp publish para volver a implementar el proyecto y reemplace<APP_NAME> el nombre de la aplicación.

func azure functionapp publish <APP_NAME>

En la carpeta de proyecto local, use el siguiente comando de Maven para volver a publicar el proyecto:

mvn azure-functions:deploy

Comprobación en Azure

  1. Como en el inicio rápido anterior, use un explorador o CURL para probar la función que ha vuelto a implementar.

    Copie la dirección URL de invocación completa que se muestra en la salida del comando de publicación en una barra de direcciones del explorador, y anexe el parámetro de consulta &name=Functions. El explorador debe mostrar la misma salida a cuando ejecutó la función localmente.

  2. Vuelva a examinar la cola de Storage, como se describe en la sección anterior, para comprobar que contiene el nuevo mensaje escrito en la cola.

Limpieza de recursos

Cuando haya terminado, use el siguiente comando para eliminar el grupo de recursos y todos los recursos que contiene para evitar incurrir en costos adicionales.

az group delete --name AzureFunctionsQuickstart-rg

Pasos siguientes

Ha actualizado la función desencadenada por HTTP para escribir datos en una cola de almacenamiento. Ahora puede aprender más sobre el desarrollo de Functions desde la línea de comandos mediante Core Tools y la CLI de Azure: