Associazioni HTTP e webhook in Funzioni di Azure

Questo articolo illustra come configurare e usare trigger e associazioni HTTP in Funzioni di Azure. In questo modo è possibile usare Funzioni di Azure per compilare API senza server e rispondere ai webhook.

Funzioni di Azure fornisce le associazioni seguenti:

Informazioni di riferimento per gli sviluppatori delle Funzioni di Azure. Se non si ha familiarità con le Funzioni di Azure, iniziare con le seguenti risorse:

Suggerimento

Se si prevede di usare binding HTTP o WebHook, evitare l'esaurimento delle porte che può essere causato da un'errata creazione di istanze di HttpClient. Per altre informazioni, vedere l'articolo Improper Instantiation antipattern (Antipattern non valido per la creazione di istanze).

Trigger HTTP

Il trigger HTTP eseguirà la funzione in risposta a una richiesta HTTP. È possibile personalizzarlo per rispondere a un particolare URL o set di metodi HTTP. Un trigger HTTP può essere configurato anche per rispondere ai webhook.

Se si usa il portale di Funzioni, è anche possibile iniziare subito a usare un modello predefinito. Selezionare Nuova funzione e scegliere "API e webhook" dal menu a discesa Scenario. Selezionare uno dei modelli e fare clic su Crea.

Per impostazione predefinita, un trigger HTTP risponderà alla richiesta con un codice di stato HTTP 200 OK e un corpo vuoto. Per modificare la risposta, configurare un'associazione di output HTTP

Configurazione di un trigger HTTP

Un trigger HTTP viene definito includendo un oggetto JSON simile al seguente nella matrice bindings di function.json:

{
    "name": "req",
    "type": "httpTrigger",
    "direction": "in",
    "authLevel": "function",
    "methods": [ "get" ],
    "route": "values/{id}"
},

L'associazione supporta le proprietà seguenti:

  • name: obbligatoria. Nome della variabile usato nel codice della funzione per la richiesta o il corpo della richiesta. Vedere Uso di un trigger HTTP dal codice.
  • type: obbligatoria. Deve essere impostata su "httpTrigger".
  • direction: obbligatoria. Deve essere impostata su "in".
  • authLevel: determina le eventuali chiavi che devono essere presenti nella richiesta per richiamare la funzione. Vedere Uso delle chiavi più avanti. Il valore può essere uno dei seguenti:
    • anonymous: nessuna chiave API obbligatoria.
    • function: è obbligatoria una chiave API specifica della funzione. Questo è il valore predefinito se non ne viene specificato nessuno.
    • admin: la chiave master è obbligatoria.
  • methods: matrice dei metodi HTTP a cui la funzione risponderà. Se non viene specificata, la funzione risponderà a tutti i metodi HTTP. Vedere Personalizzazione dell'endpoint HTTP.
  • route: definisce il modello di route, controllando a quali URL delle richieste la funzione risponderà. Il valore predefinito, se non ne viene specificato nessuno, è <functionname>. Vedere Personalizzazione dell'endpoint HTTP.
  • webHookType: configura il trigger HTTP perché funga da ricevitore webhook per il provider specificato. Se viene scelto questo valore, la proprietà methods non deve essere impostata. Vedere Risposta ai webhook. Il valore può essere uno dei seguenti:
    • genericJson: endpoint di webhook per utilizzo generico senza logica per un provider specifico.
    • github: la funzione risponderà ai webhook GitHub. Se viene scelto questo valore, la proprietà authLevel non deve essere impostata.
    • slack: la funzione risponderà ai webhook Slack. Se viene scelto questo valore, la proprietà authLevel non deve essere impostata.

Uso di un trigger HTTP dal codice

Per le funzioni C# e F#, è possibile dichiarare HttpRequestMessage o un tipo personalizzato come tipo dell'input del trigger. Se si sceglie HttpRequestMessage, si otterrà l'accesso completo all'oggetto richiesta. Per un tipo personalizzato (ad esempio, POCO), Funzioni cercherà di analizzare il corpo della richiesta come JSON per popolare le proprietà dell'oggetto.

Per le funzioni Node.js, il runtime di Funzioni fornisce il corpo della richiesta invece dell'oggetto richiesta.

Vedere Esempi di trigger HTTP per gli utilizzi di esempio.

Associazione di output della risposta HTTP

Usare l'associazione di output HTTP per rispondere al mittente della richiesta HTTP. Questa associazione richiede un trigger HTTP e consente di personalizzare la risposta associata alla richiesta del trigger. Se non viene specificata un'associazione di output HTTP, un trigger HTTP restituirà HTTP 200 OK con un corpo vuoto.

Configurazione di un'associazione di output HTTP

L'associazione di output HTTP viene definita includendo un oggetto JSON simile al seguente nella matrice bindings di function.json:

{
    "name": "res",
    "type": "http",
    "direction": "out"
}

L'associazione contiene le proprietà seguenti:

  • name: obbligatoria. Nome della variabile usato nel codice della funzione per la risposta. Vedere Uso di un'associazione di output HTTP dal codice.
  • type: obbligatoria. Deve essere impostata su "http".
  • direction: obbligatoria. Deve essere impostata su "out".

Uso di un'associazione di output HTTP dal codice

È possibile usare il parametro di output (ad esempio, "res") per rispondere al chiamante HTTP o webhook. In alternativa, è possibile usare il modello standard Request.CreateResponse() (C#) o context.res (Node.JS) per restituire la risposta. Per esempi di come usare il secondo metodo, vedere Esempi di trigger HTTP ed Esempi di trigger webhook.

Risposta ai webhook

Un trigger HTTP con la proprietà webHookType verrà configurato per rispondere ai webhook. La configurazione di base usa l'impostazione "genericJson", che limita le richieste solo a quelle che usano HTTP POST e con il tipo di contenuto application/json.

Il trigger può anche essere personalizzato per un provider di webhook specifico (ad esempio, GitHub e Slack). Se viene specificato un provider, il runtime di Funzioni può eseguire automaticamente la logica di convalida del provider.

Configurazione di GitHub come provider di webhook

Per rispondere ai webhook GitHub, creare prima di tutto la funzione con un trigger HTTP e impostare la proprietà webHookType su "github". Copiare quindi l'URL e la chiave API nella pagina Add webhook (Aggiungi webhook) del repository GitHub. Per altre informazioni, vedere la documentazione Creating Webhooks (Creazione di webhook) di GitHub.

Configurazione di Slack come provider di webhook

Il webhook Slack genera automaticamente un token invece di consentire di specificarlo, quindi è necessario configurare una chiave specifica della funzione con il token da Slack. Vedere Uso delle chiavi.

Personalizzazione dell'endpoint HTTP

Per impostazione predefinita, quando si crea una funzione per un trigger HTTP o un webhook, la funzione può essere indirizzata con una route nel formato seguente:

http://<yourapp>.azurewebsites.net/api/<funcname> 

È possibile personalizzare questa route tramite la proprietà route facoltativa nell'associazione di input del trigger HTTP. Ad esempio, il file function.json seguente definisce una proprietà route per un trigger HTTP:

    {
      "bindings": [
        {
          "type": "httpTrigger",
          "name": "req",
          "direction": "in",
          "methods": [ "get" ],
          "route": "products/{category:alpha}/{id:int?}"
        },
        {
          "type": "http",
          "name": "res",
          "direction": "out"
        }
      ]
    }

Con questa configurazione, la funzione può ora essere indirizzata con la route seguente invece che con quella originale.

http://<yourapp>.azurewebsites.net/api/products/electronics/357

In questo modo il codice della funzione può supportare due parametri nell'indirizzo: "category" e "id". I parametri sono compatibili con qualsiasi vincolo di route dell'API Web. Il codice di funzione C# seguente usa entrambi i parametri.

    public static Task<HttpResponseMessage> Run(HttpRequestMessage req, string category, int? id, 
                                                    TraceWriter log)
    {
        if (id == null)
           return  req.CreateResponse(HttpStatusCode.OK, $"All {category} items were requested.");
        else
           return  req.CreateResponse(HttpStatusCode.OK, $"{category} item with id = {id} has been requested.");
    }

Di seguito è mostrato il codice di funzione Node.js per usare gli stessi parametri di route.

    module.exports = function (context, req) {

        var category = context.bindingData.category;
        var id = context.bindingData.id;

        if (!id) {
            context.res = {
                // status: 200, /* Defaults to 200 */
                body: "All " + category + " items were requested."
            };
        }
        else {
            context.res = {
                // status: 200, /* Defaults to 200 */
                body: category + " item with id = " + id + " was requested."
            };
        }

        context.done();
    } 

Per impostazione predefinita, tutte le route di funzione sono precedute da api. È inoltre possibile personalizzare o rimuovere il prefisso con la proprietà http.routePrefix nel file host.json. Nell'esempio seguente viene rimosso il prefisso della route api usando una stringa vuota per il prefisso nel file host.json.

    {
      "http": {
        "routePrefix": ""
      }
    }

Per informazioni dettagliate su come aggiornare il file host.json per la funzione, vedere Come aggiornare i file nelle app per le funzioni.

Per informazioni su altre proprietà che è possibile configurare nel file host.json, vedere il riferimento su host.json.

Uso delle chiavi

Gli elementi HttpTrigger possono sfruttare le chiavi per una maggiore sicurezza. Un elemento HttpTrigger standard può usarle come chiave API, imponendo la presenza della chiave nella richiesta. I webhook possono usare le chiavi per autorizzare le richieste in svariati modi, a seconda di ciò che il provider supporta.

Le chiavi vengono archiviate come parte dell'app per le funzioni in Azure e crittografate inattive. Per visualizzare le chiavi, crearne di nuove o aggiornare le chiavi con nuovi valori, passare a una delle funzioni nel portale e selezionare "Gestisci".

Esistono due tipi di chiavi:

  • Chiavi host: queste chiavi vengono condivise da tutte le funzioni nell'app per le funzioni. Quando vengono usate come chiave API, consentono l'accesso a tutte le funzioni nell'app per le funzioni.
  • Chiavi di funzione: queste chiavi si applicano solo alle funzioni specifiche sotto le quali vengono definite. Quando vengono usate come chiave API, consentono l'accesso solo a tale funzione.

Ogni chiave viene denominata per riferimento ed esiste una chiave predefinita (denominata "default") a livello di funzione e di host. La chiave master è una chiave host predefinita denominata "_master" che viene definita per ogni app per le funzioni e non può essere revocata. Fornisce l'accesso amministrativo alle API di runtime. Per usare "authLevel": "admin" nel file JSON di associazione, nella richiesta dovrà essere presentata questa chiave. Qualsiasi altra chiave restituirà un errore di autorizzazione.

Nota

Date le autorizzazioni elevate concesse dalla chiave master, è consigliabile non condividere questa chiave con terze parti o distribuirla in applicazioni client native. Prestare attenzione quando si sceglie il livello di autorizzazione di amministratore.

Autorizzazione della chiave API

Per impostazione predefinita, un elemento HttpTrigger richiede una chiave API nella richiesta HTTP. La richiesta HTTP in genere è quindi simile alla seguente:

https://<yourapp>.azurewebsites.net/api/<function>?code=<ApiKey>

La chiave può essere inclusa in una variabile della stringa di query denominata code, come sopra, oppure in un'intestazione HTTP x-functions-key. Il valore della chiave può essere una chiave di funzione definita per la funzione o una chiave host.

È possibile scegliere di consentire le richieste senza chiavi o specificare che deve essere usata la chiave master modificando la proprietà authLevel nel file JSON dell'associazione. Vedere Trigger HTTP.

Chiavi e webhook

L'autorizzazione webhook viene gestita dal componente ricevitore dei webhook, che fa parte di HttpTrigger, e il meccanismo varia in base al tipo di webhook. Ogni meccanismo tuttavia si basa su una chiave. Per impostazione predefinita, verrà usata la chiave di funzione denominata "default". Per usare un'altra chiave, sarà necessario configurare il provider di webhook per inviare il nome della chiave con la richiesta in uno dei modi seguenti:

  • Stringa di query: il provider passa il nome della chiave nel parametro della stringa di query clientid (ad esempio, https://<yourapp>.azurewebsites.net/api/<funcname>?clientid=<keyname>).
  • Intestazione della richiesta: il provider passa il nome della chiave nell'intestazione x-functions-clientid.
Nota

Le chiavi di funzione hanno la precedenza sulle chiavi host. Se due chiavi sono definite con lo stesso nome, verrà usata la chiave di funzione.

Esempi di trigger HTTP

Si supponga che il trigger HTTP seguente sia presente nella matrice bindings di function.json:

{
    "name": "req",
    "type": "httpTrigger",
    "direction": "in",
    "authLevel": "function"
},

Vedere l'esempio specifico del linguaggio, che cerca un parametro name nella stringa di query o nel corpo della richiesta HTTP.

Esempio di trigger HTTP in C#

using System.Net;
using System.Threading.Tasks;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    log.Info($"C# HTTP trigger function processed a request. RequestUri={req.RequestUri}");

    // parse query parameter
    string name = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
        .Value;

    // Get request body
    dynamic data = await req.Content.ReadAsAsync<object>();

    // Set name to query string or body data
    name = name ?? data?.name;

    return name == null
        ? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
        : req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}

È anche possibile eseguire l'associazione a un POCO invece di HttpRequestMessage. POCO sarà idratato dal corpo della richiesta e analizzato come JSON. Analogamente, un tipo può essere passato all'associazione dell'output di risposta HTTP e verrà restituito come corpo della risposta, con un codice di stato 200.

using System.Net;
using System.Threading.Tasks;

public static string Run(CustomObject req, TraceWriter log)
{
    return "Hello " + req?.name;
}

public class CustomObject {
     public String name {get; set;}
}
}

Esempio di trigger HTTP in F#

open System.Net
open System.Net.Http
open FSharp.Interop.Dynamic

let Run(req: HttpRequestMessage) =
    async {
        let q =
            req.GetQueryNameValuePairs()
                |> Seq.tryFind (fun kv -> kv.Key = "name")
        match q with
        | Some kv ->
            return req.CreateResponse(HttpStatusCode.OK, "Hello " + kv.Value)
        | None ->
            let! data = Async.AwaitTask(req.Content.ReadAsAsync<obj>())
            try
                return req.CreateResponse(HttpStatusCode.OK, "Hello " + data?name)
            with e ->
                return req.CreateErrorResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
    } |> Async.StartAsTask

È necessario un file project.json che usa NuGet per fare riferimento agli assembly FSharp.Interop.Dynamic e Dynamitey come segue:

{
  "frameworks": {
    "net46": {
      "dependencies": {
        "Dynamitey": "1.0.2",
        "FSharp.Interop.Dynamic": "3.0.0"
      }
    }
  }
}

Verrà usato NuGet per recuperare le dipendenze e verrà creato un riferimento alle dipendenze nello script.

Esempio di trigger HTTP in Node.JS

module.exports = function(context, req) {
    context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);

    if (req.query.name || (req.body && req.body.name)) {
        context.res = {
            // status: 200, /* Defaults to 200 */
            body: "Hello " + (req.query.name || req.body.name)
        };
    }
    else {
        context.res = {
            status: 400,
            body: "Please pass a name on the query string or in the request body"
        };
    }
    context.done();
};

Esempi di webhook

Si supponga che il trigger webhook seguente sia presente nella matrice bindings di function.json:

{
    "webHookType": "github",
    "name": "req",
    "type": "httpTrigger",
    "direction": "in",
},

Vedere l'esempio specifico del linguaggio che registra i commenti del problema GitHub.

Esempio di webhook in C#

#r "Newtonsoft.Json"

using System;
using System.Net;
using System.Threading.Tasks;
using Newtonsoft.Json;

public static async Task<object> Run(HttpRequestMessage req, TraceWriter log)
{
    string jsonContent = await req.Content.ReadAsStringAsync();
    dynamic data = JsonConvert.DeserializeObject(jsonContent);

    log.Info($"WebHook was triggered! Comment: {data.comment.body}");

    return req.CreateResponse(HttpStatusCode.OK, new {
        body = $"New GitHub comment: {data.comment.body}"
    });
}

Esempio di webhook in F#

open System.Net
open System.Net.Http
open FSharp.Interop.Dynamic
open Newtonsoft.Json

type Response = {
    body: string
}

let Run(req: HttpRequestMessage, log: TraceWriter) =
    async {
        let! content = req.Content.ReadAsStringAsync() |> Async.AwaitTask
        let data = content |> JsonConvert.DeserializeObject
        log.Info(sprintf "GitHub WebHook triggered! %s" data?comment?body)
        return req.CreateResponse(
            HttpStatusCode.OK,
            { body = sprintf "New GitHub comment: %s" data?comment?body })
    } |> Async.StartAsTask

Esempio di webhook in Node.JS

module.exports = function (context, data) {
    context.log('GitHub WebHook triggered!', data.comment.body);
    context.res = { body: 'New GitHub comment: ' + data.comment.body };
    context.done();
};

Passaggi successivi

Per informazioni su altre associazioni e altri trigger per Funzioni di Azure, vedere Guida di riferimento per gli sviluppatori di trigger e associazioni di Funzioni di Azure.