Compartir a través de


Controladores personalizados de Azure Functions

Azure Functions ejecuta el código de la aplicación mediante controladores específicos del lenguaje. Estos controladores específicos del lenguaje permiten que Functions admita la mayoría de los lenguajes clave de forma predeterminada. Sin embargo, es posible que tenga que ejecutar código en otro lenguaje o paquete.

Los controladores personalizados son servidores web ligeros que reciben eventos del proceso de host de Azure Functions. Puede usar controladores personalizados para implementar en Azure Functions cualquier proyecto de código que admita primitivos HTTP.

Los controladores personalizados son más adecuados para las situaciones en las que desea:

  • Implemente una aplicación de funciones en un lenguaje que no se ofrece actualmente de forma predeterminada, como Go o Rust.
  • Implemente una aplicación de funciones en un runtime que actualmente no se incluye de manera predeterminada, como Deno.
  • Implemente un servidor creado con los SDK de MCP estándar en Azure Functions.

Con los controladores personalizados, puede usar desencadenadores y enlaces de entrada y salida mediante conjuntos de extensiones.

Introducción a los controladores personalizados de Azure Functions con inicios rápidos en Go y Rust.

Información general

En el diagrama siguiente se muestra la relación entre el host de Functions y un servidor web implementado como controlador personalizado.

Introducción a los controladores personalizados de Azure Functions

  1. Cada evento desencadena una solicitud enviada al host de Functions. Un evento es cualquier desencadenador compatible con Azure Functions.
  2. A continuación, el host de Functions emite una carga de la solicitud al servidor web. La carga contiene datos de enlace de entrada y del desencadenador, así como otros metadatos para la función.
  3. El servidor web ejecuta la función individual y devuelve una carga de respuesta al host de Functions.
  4. El host de Functions pasa datos de la respuesta a los enlaces de salida de la función para su procesamiento.

Una aplicación Azure Functions implementada como controlador personalizado debe configurar los archivos host.json, local.settings.json y function.json siguiendo algunas convenciones.

Implementación de servidores MCP autohospedados

Los controladores personalizados también le permiten hospedar servidores MCP que se compilan mediante SDK de MCP oficiales en Azure Functions. Los controladores personalizados proporcionan una experiencia sencilla y simplificada para hospedar los servidores MCP en Azure. Para más información, consulte Servidor MCP remoto autohospedado en Azure Functions.

Nota:

La capacidad de alojar servidores MCP en Azure Functions que se crean mediante los SDK oficiales de MCP está actualmente en versión preliminar.

Estructura de la aplicación

Para implementar un controlador personalizado, la aplicación necesita los siguientes aspectos:

  • Un archivo host.json en la raíz de la aplicación
  • Un archivo local.settings.json en la raíz de la aplicación
  • Un archivo function.json para cada función (dentro de una carpeta que coincida con el nombre de función)
  • Comando, script o ejecutable que ejecuta un servidor web

En el diagrama siguiente, se muestra cómo estos archivos buscan en el sistema de archivos una función denominada "MyQueueFunction" y un ejecutable del controlador personalizado denominado handler.exe.

| /MyQueueFunction
|   function.json
|
| host.json
| local.settings.json
| handler.exe

Configuración

Configure la aplicación a través de los archivos host.json y local.settings.json .

host.json

host.json dirige el host de Functions donde enviar solicitudes apuntando a un servidor web que puede procesar eventos HTTP.

Defina un controlador personalizado configurando el archivo host.json con detalles sobre cómo ejecutar el servidor web a través de la customHandler sección .

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "handler.exe"
    }
  }
}

La sección customHandler apunta a un destino tal y como se define en defaultExecutablePath. El destino de ejecución puede ser un comando, ejecutable o archivo donde se implementa el servidor web.

Use la matriz arguments para pasar cualquier argumento al ejecutable. Los argumentos admiten la expansión de variables de entorno (configuración de la aplicación) mediante %% notación.

También puede cambiar el directorio de trabajo que usa el ejecutable con workingDirectory.

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "app/handler.exe",
      "arguments": [
        "--database-connection-string",
        "%DATABASE_CONNECTION_STRING%"
      ],
      "workingDirectory": "app"
    }
  }
}
Compatibilidad con enlaces

Los desencadenadores estándar junto con los enlaces de entrada y salida están disponibles al hacer referencia a los conjuntos de extensión en el archivo host.json.

local.settings.json

local.settings.json define la configuración de la aplicación que se usa al ejecutar la aplicación de funciones localmente. Dado que puede contener secretos, excluya local.settings.json del control de código fuente. En Azure, use la configuración de la aplicación en su lugar.

En el caso de los controladores personalizados, establezca FUNCTIONS_WORKER_RUNTIME en Custom en local.settings.json.

{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "Custom"
  }
}

Metadatos de función

Cuando se usa un controlador personalizado, el contenido delfunction.json es el mismo que cuando se define una función en cualquier otro contexto. El único requisito es que debe colocar function.json archivos en una carpeta denominada para que coincida con el nombre de la función.

El siguiente archivo function.json configura una función que tiene un desencadenador de cola y un enlace de salida de cola. Dado que se encuentra en una carpeta denominada MyQueueFunction, define una función llamada MyQueueFunction.

MyQueueFunction/function.json

{
  "bindings": [
    {
      "name": "myQueueItem",
      "type": "queueTrigger",
      "direction": "in",
      "queueName": "messages-incoming",
      "connection": "AzureWebJobsStorage"
    },
    {
      "name": "$return",
      "type": "queue",
      "direction": "out",
      "queueName": "messages-outgoing",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

Carga de solicitud

Cuando el host de Functions recibe un mensaje de cola, envía una solicitud HTTP POST al controlador personalizado con una carga útil en el cuerpo de la solicitud.

En el código siguiente, se muestra una carga de solicitud de ejemplo. La carga incluye una estructura JSON con dos miembros: Data y Metadata.

El miembro Data incluye claves que coinciden con los nombres de los desencadenadores y de entrada, tal y como se definen en la matriz de enlaces del archivo function.json.

El miembro Metadata incluye metadatos generados a partir del origen del evento.

{
  "Data": {
    "myQueueItem": "{ message: \"Message sent\" }"
  },
  "Metadata": {
    "DequeueCount": 1,
    "ExpirationTime": "2019-10-16T17:58:31+00:00",
    "Id": "800ae4b3-bdd2-4c08-badd-f08e5a34b865",
    "InsertionTime": "2019-10-09T17:58:31+00:00",
    "NextVisibleTime": "2019-10-09T18:08:32+00:00",
    "PopReceipt": "AgAAAAMAAAAAAAAAAgtnj8x+1QE=",
    "sys": {
      "MethodName": "QueueTrigger",
      "UtcNow": "2019-10-09T17:58:32.2205399Z",
      "RandGuid": "24ad4c06-24ad-4e5b-8294-3da9714877e9"
    }
  }
}

Carga de respuesta

Por convención, las respuestas de función tienen el formato de pares clave-valor. Las claves admitidas son:

Clave de carga Tipo de datos Observaciones
Outputs object Contiene los valores de respuesta definidos por la matriz bindings del archivo function.json.

Por ejemplo, si una función está configurada con un enlace de salida de cola denominado "myQueueOutput", entonces Outputs contiene una clave denominada myQueueOutput, la cual el controlador personalizado utiliza para los mensajes que envía a la cola.
Logs array Mensajes que aparecen en los registros de invocación de Functions.

Cuando se ejecuta en Azure, los mensajes aparecen en Application Insights.
ReturnValue string Se usa para proporcionar una respuesta cuando se configura una salida como $return en el archivo function.json.

En esta tabla se muestra un ejemplo de una carga de respuesta.

{
  "Outputs": {
    "res": {
      "body": "Message enqueued"
    },
    "myQueueOutput": [
      "queue message 1",
      "queue message 2"
    ]
  },
  "Logs": [
    "Log message 1",
    "Log message 2"
  ],
  "ReturnValue": "{\"hello\":\"world\"}"
}

Ejemplos

Puede implementar controladores personalizados en cualquier lenguaje que admita la recepción de eventos HTTP. En los ejemplos siguientes se muestra cómo implementar un controlador personalizado mediante el lenguaje de programación Go.

Función con enlaces

En este ejemplo se muestra una función denominada order que acepta una POST solicitud con una carga que representa un pedido de producto. Cuando publica un pedido en la función, crea un mensaje de Queue Storage y devuelve una respuesta HTTP.

Implementación

En una carpeta denominada order, el archivo function.json configura la función desencadenada por HTTP.

order/function.json

{
  "bindings": [
    {
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["post"]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    },
    {
      "type": "queue",
      "name": "message",
      "direction": "out",
      "queueName": "orders",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

Esta función se define como una función desencadenada por HTTP que devuelve una respuesta HTTP y genera un mensaje de Queue Storage.

En la raíz de la aplicación, el archivo host.json se configura para ejecutar un archivo ejecutable nombrado handler.exe (handler en Linux o macOS).

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "handler.exe"
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  }
}

Esta es la solicitud HTTP enviada al runtime de Functions.

POST http://127.0.0.1:7071/api/order HTTP/1.1
Content-Type: application/json

{
  "id": 1005,
  "quantity": 2,
  "color": "black"
}

El entorno de ejecución de Functions envía la siguiente solicitud HTTP al controlador personalizado:

POST http://127.0.0.1:<FUNCTIONS_CUSTOMHANDLER_PORT>/order HTTP/1.1
Content-Type: application/json

{
  "Data": {
    "req": {
      "Url": "http://localhost:7071/api/order",
      "Method": "POST",
      "Query": "{}",
      "Headers": {
        "Content-Type": [
          "application/json"
        ]
      },
      "Params": {},
      "Body": "{\"id\":1005,\"quantity\":2,\"color\":\"black\"}"
    }
  },
  "Metadata": {
  }
}

Nota:

Algunas partes de la carga se quitaron por motivos de brevedad.

handler.exe es el programa de controlador personalizado Go compilado que ejecuta un servidor web y responde a solicitudes de invocación de la función del host de Functions.

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
)

type InvokeRequest struct {
	Data     map[string]json.RawMessage
	Metadata map[string]interface{}
}

type InvokeResponse struct {
	Outputs     map[string]interface{}
	Logs        []string
	ReturnValue interface{}
}

func orderHandler(w http.ResponseWriter, r *http.Request) {
	var invokeRequest InvokeRequest

	d := json.NewDecoder(r.Body)
	d.Decode(&invokeRequest)

	var reqData map[string]interface{}
	json.Unmarshal(invokeRequest.Data["req"], &reqData)

	outputs := make(map[string]interface{})
	outputs["message"] = reqData["Body"]

	resData := make(map[string]interface{})
	resData["body"] = "Order enqueued"
	outputs["res"] = resData
	invokeResponse := InvokeResponse{outputs, nil, nil}

	responseJson, _ := json.Marshal(invokeResponse)

	w.Header().Set("Content-Type", "application/json")
	w.Write(responseJson)
}

func main() {
	customHandlerPort, exists := os.LookupEnv("FUNCTIONS_CUSTOMHANDLER_PORT")
	if !exists {
		customHandlerPort = "8080"
	}
	mux := http.NewServeMux()
	mux.HandleFunc("/order", orderHandler)
	fmt.Println("Go server Listening on: ", customHandlerPort)
	log.Fatal(http.ListenAndServe(":"+customHandlerPort, mux))
}

En este ejemplo, el controlador personalizado ejecuta un servidor web para controlar eventos HTTP y escucha las solicitudes a través de FUNCTIONS_CUSTOMHANDLER_PORT.

Aunque el host de Functions recibe la solicitud HTTP original en /api/order, invoca el controlador personalizado mediante el nombre de la función (su nombre de carpeta). En este ejemplo, la función se define en la ruta de acceso de /order. El host envía al controlador personalizado una solicitud HTTP en la ruta de acceso de /order.

Al enviar POST solicitudes a esta función, los datos de desencadenador y los metadatos de función están disponibles a través del cuerpo de la solicitud HTTP. Puede acceder al cuerpo de la solicitud HTTP original en el Data.req.Body de la carga útil.

El formato de la respuesta de la función se aplica a pares clave-valor, donde el miembro Outputs contiene un valor JSON en el que las claves coinciden con las salidas definidas en el archivo function.json.

Esta es una carga de ejemplo que este controlador devuelve al host de Functions.

{
  "Outputs": {
    "message": "{\"id\":1005,\"quantity\":2,\"color\":\"black\"}",
    "res": {
      "body": "Order enqueued"
    }
  },
  "Logs": null,
  "ReturnValue": null
}

Al establecer la salida message equivalente a los datos del pedido procedentes de la solicitud, la función genera dichos datos del pedido en la cola configurada. El host de Functions también devuelve la respuesta HTTP configurada en res al llamador.

Función de solo HTTP

En el caso de las funciones desencadenadas por HTTP sin enlaces ni salidas adicionales, es posible que desee que el controlador funcione directamente con la solicitud y la respuesta HTTP en lugar de las cargas de respuesta y solicitud de controlador personalizadas. Puede configurar este comportamiento en host.json mediante la enableProxyingHttpRequest configuración , que admite el streaming de respuesta.

Importante

El propósito principal de la característica de controladores personalizados es habilitar lenguajes y entornos de ejecución que actualmente no tienen compatibilidad de primera clase en Azure Functions. Aunque es posible que pueda ejecutar aplicaciones web mediante controladores personalizados, Azure Functions no es un proxy inverso estándar. Algunos componentes de la solicitud HTTP, como determinados encabezados y rutas, pueden estar restringidos. La aplicación también puede experimentar un arranque en frío excesivo.

Para abordar estas circunstancias, considere la posibilidad de ejecutar las aplicaciones web en Azure App Service.

En el ejemplo siguiente se muestra cómo configurar una función desencadenada por HTTP sin enlaces ni salidas adicionales. El escenario implementado en este ejemplo incluye una función denominada hello que acepta GET o POST.

Implementación

En una carpeta denominada hello, el archivo function.json configura la función desencadenada por HTTP.

hello/function.json

{
  "bindings": [
    {
      "type": "httpTrigger",
      "authLevel": "anonymous",
      "direction": "in",
      "name": "req",
      "methods": ["get", "post"]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ]
}

La función está configurada para aceptar GET y POST solicitudes, y el valor de resultado se proporciona a través de un argumento denominado res.

En la raíz de la aplicación, el archivo host.json está configurado para ejecutar handler.exe y enableProxyingHttpRequest se establece en true.

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "handler.exe"
    },
    "enableProxyingHttpRequest": true
  }
}

A continuación se muestra una solicitud POST al host de Functions. A continuación, el host de Functions envía la solicitud al controlador personalizado.

POST http://127.0.0.1:7071/api/hello HTTP/1.1
Content-Type: application/json

{
  "message": "Hello World!"
}

El archivo handler.go implementa un servidor web y una función HTTP.

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	if r.Method == "GET" {
		w.Write([]byte("hello world"))
	} else {
		body, _ := ioutil.ReadAll(r.Body)
		w.Write(body)
	}
}

func main() {
	customHandlerPort, exists := os.LookupEnv("FUNCTIONS_CUSTOMHANDLER_PORT")
	if !exists {
		customHandlerPort = "8080"
	}
	mux := http.NewServeMux()
	mux.HandleFunc("/api/hello", helloHandler)
	fmt.Println("Go server Listening on: ", customHandlerPort)
	log.Fatal(http.ListenAndServe(":"+customHandlerPort, mux))
}

En este ejemplo, el controlador personalizado crea un servidor web para controlar eventos HTTP y escucha las solicitudes a través de FUNCTIONS_CUSTOMHANDLER_PORT.

Las solicitudes GET se administran al devolver una cadena y las solicitudes POST tienen acceso al cuerpo de la solicitud.

La ruta para la función Order aquí es /api/hello, igual que la solicitud original.

Nota:

FUNCTIONS_CUSTOMHANDLER_PORT no es el puerto de acceso público que se usa para llamar a la función. El host de Functions usa este puerto para llamar al controlador personalizado.

Implementando

Puede implementar un controlador personalizado en cada opción de hospedaje de Azure Functions. Si el controlador requiere dependencias del sistema operativo o de la plataforma (como Language Runtime), es posible que tenga que usar un contenedor personalizado.

Al crear una aplicación de funciones en Azure para controladores personalizados, seleccione .NET Core como pila.

Para implementar una aplicación de controlador personalizada mediante Azure Functions Core Tools, ejecute el siguiente comando.

func azure functionapp publish $functionAppName

Nota:

Asegúrese de que todos los archivos necesarios para ejecutar el controlador personalizado están en la carpeta y se incluyen en la implementación. Si el controlador personalizado es un archivo ejecutable binario o tiene dependencias específicas de la plataforma, asegúrese de que estos archivos coinciden con la plataforma de implementación de destino.

Restricciones

  • El servidor web del controlador personalizado se debe iniciar en un plazo de 60 segundos.

Ejemplos

Para obtener ejemplos de cómo implementar funciones en una variedad de lenguajes diferentes, consulte el repositorio de GitHub de ejemplos de controladores personalizados.

Solución de problemas y soporte técnico

Registro de seguimiento

Si el proceso del controlador personalizado no se inicia o tiene problemas para comunicarse con el host de Functions, aumente el nivel de registro de la aplicación de funciones a Trace para ver más mensajes de diagnóstico del host.

Para cambiar el nivel de registro predeterminado de la aplicación de funciones, establezca la configuración logLevel en la sección logging de host.json.

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "handler.exe"
    }
  },
  "logging": {
    "logLevel": {
      "default": "Trace"
    }
  }
}

El host de Functions genera mensajes de registro adicionales, incluida la información relacionada con el proceso de controlador personalizado. Use los registros para investigar problemas al iniciar el proceso del controlador personalizado o invocar funciones en este.

Localmente, los registros se imprimen en la consola.

En Azure, consulte los seguimientos de Application Insights para ver los mensajes de registro. Si la aplicación genera un gran volumen de registros, solo se enviará un subconjunto de mensajes de registro a Application Insights. Deshabilite el muestreo para asegurarse de que se registran todos los mensajes.

Prueba del controlador personalizado en aislamiento

Las aplicaciones de controlador personalizadas son procesos de servidor web, por lo que puede resultar útil iniciarlas por sí mismas y probar invocaciones de función mediante el envío de solicitudes HTTP simuladas. Para enviar solicitudes HTTP con cargas, asegúrese de elegir una herramienta que mantenga los datos seguros. Para obtener más información, consulte Herramientas de prueba HTTP.

También puede usar esta estrategia en las canalizaciones de CI/CD para ejecutar pruebas automatizadas en el controlador personalizado.

Entorno de ejecución

Los controladores personalizados se ejecutan en el mismo entorno que una aplicación de Azure Functions típica. Pruebe el controlador para asegurarse de que el entorno contiene todas las dependencias que necesita para ejecutarse. En el caso de las aplicaciones que requieren dependencias adicionales, es posible que tenga que ejecutarlas mediante una imagen de contenedor personalizada hospedada en el plan Premium de Azure Functions.

Obtención de soporte técnico

Si necesita ayuda en una aplicación de funciones con controladores personalizados, puede enviar una solicitud a través de canales de soporte técnico normales. Sin embargo, debido a la amplia variedad de lenguajes posibles que se usan para crear aplicaciones de controladores personalizados, la compatibilidad no es ilimitada.

El soporte técnico estará disponible si el host de Functions tiene problemas para iniciarse o comunicarse con el proceso del controlador personalizado. Para problemas específicos del funcionamiento interno del proceso de controlador personalizado, como problemas con el lenguaje o marco elegido, nuestro equipo de soporte técnico no puede proporcionar asistencia en este contexto.

Pasos siguientes

Comience a crear una aplicación de Azure Functions en Go o Rust en el inicio rápido de controladores personalizados.