Condividi tramite


Uso di servizi esterni dal servizio Gestione API di Azure

SI APPLICA A: tutti i livelli di Gestione API

I criteri disponibili nel servizio Gestione API di Azure possono essere usati per una vasta gamma di attività basate esclusivamente su richieste in ingresso, risposte in uscita e informazioni di configurazione di base. Tuttavia, la possibilità di interagire con i servizi esterni dai criteri di Gestione API offre molte altre opportunità.

Negli articoli precedenti è stato illustrato come interagire con il servizio Hub eventi di Azure per la registrazione, il monitoraggio e l'analisi. In questo articolo vengono descritti i criteri che consentono di interagire con qualsiasi servizio esterno basato su HTTP. Questi criteri possono essere usati per l'attivazione di eventi remoti o per il recupero di informazioni che vengono usate per gestire la richiesta e la risposta originali.

Send-One-Way-Request

Probabilmente l'interazione esterna più semplice è lo stile di richiesta fire-and-forget che consente a un servizio esterno di ricevere una notifica di qualche tipo di evento importante. I criteri choose del flusso di controllo possono essere usati per rilevare qualsiasi tipo di condizione a cui si è interessati. Se la condizione è soddisfatta, è possibile inviare una richiesta HTTP esterna usando i criteri send-one-way-request. Questa richiesta può essere inviata a un sistema di messaggistica come Hipchat o Slack o a un'API di posta elettronica come SendGrid o MailChimp o per eventi imprevisti di supporto critici, ad esempio PagerDuty. Tutti questi sistemi di messaggistica dispongono di API HTTP semplici che è possibile richiamare.

Avvisi con Slack

L'esempio seguente illustra come inviare un messaggio a una chat di Slack, se il codice di stato della risposta HTTP è maggiore o uguale a 500. Un errore di intervallo 500 indica un problema con l'API back-end che il client dell'API non può risolvere automaticamente. In genere richiede un certo tipo di intervento nella parte di Gestione API.

<choose>
  <when condition="@(context.Response.StatusCode >= 500)">
    <send-one-way-request mode="new">
      <set-url>https://hooks.slack.com/services/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</set-url>
      <set-method>POST</set-method>
      <set-body>@{
        return new JObject(
          new JProperty("username","APIM Alert"),
          new JProperty("icon_emoji", ":ghost:"),
          new JProperty("text", String.Format("{0} {1}\nHost: {2}\n{3} {4}\n User: {5}",
            context.Request.Method,
            context.Request.Url.Path + context.Request.Url.QueryString,
            context.Request.Url.Host,
            context.Response.StatusCode,
            context.Response.StatusReason,
            context.User.Email
          ))
        ).ToString();
      }</set-body>
    </send-one-way-request>
  </when>
</choose>

In Slack sono disponibili gli hook Web in ingresso. Quando configura un web hook in ingresso, Slack genera un URL speciale, che consente di eseguire una richiesta POST di base e di passare un messaggio nel canale Slack. Il corpo JSON creato è basato su un formato definito da Slack.

Screenshot che mostra un webhook di Slack.

Lo stile di richiesta fire-and-forget è sufficientemente efficace?

Quando si usa uno stile di richiesta fire-and-forget è necessario tenere presenti alcuni svantaggi. Se per qualche motivo, la richiesta ha esito negativo, l'errore non viene segnalato. In tale situazione, la complessità di un sistema di segnalazione degli errori secondari e il costo in termini di prestazioni del tempo di attesa per la risposta non sono giustificati. Per gli scenari in cui è essenziale controllare la risposta, il criterio send-request è un'opzione migliore.

send-request

I criteri send-request consentono l'utilizzo di un servizio esterno per eseguire funzioni di elaborazione complesse e restituire dati al servizio Gestione API, che può essere usato per un'elaborazione successiva dei criteri.

Autorizzazione di token di riferimento

Una funzione fondamentale di Gestione API è la protezione delle risorse back-end. Se il server di autorizzazione utilizzato dalla tua API crea JSON Web Tokens (JWT) come parte del flusso OAuth2, come fa Microsoft Entra ID, allora puoi utilizzare la politica validate-jwt o la politica validate-azure-ad-token per verificare la validità del token. Alcuni server di autorizzazione creano i token di riferimento chiamati token di riferimento che non possono essere verificati senza effettuare un callback al server di autorizzazione.

Introspezione standardizzata

In passato non esiste un modo standardizzato per verificare un token di riferimento con un server di autorizzazione. Tuttavia, l'IETF (Internet Engineering Task Force) ha recentemente pubblicato lo standard RFC 7662 proposto che definisce come un server di risorse può verificare la validità di un token.

Estrazione del token

Il primo passaggio consiste nell'estrarre il token dall'intestazione di autorizzazione. Il valore dell'intestazione deve essere costituito dallo schema di autorizzazione Bearer, uno spazio singolo e il token di autorizzazione, in base allo standard RFC 6750. Purtroppo esistono casi in cui lo schema di autorizzazione viene omesso. Per tenere conto di questa omissione durante l'analisi, Gestione API suddivide il valore dell'intestazione in uno spazio e seleziona l'ultima stringa dalla matrice restituita di stringhe. Questo metodo fornisce una soluzione alternativa per le intestazioni di autorizzazione formattate in modo non corretto.

<set-variable name="token" value="@(context.Request.Headers.GetValueOrDefault("Authorization","scheme param").Split(' ').Last())" />

Invio della richiesta di convalida

Dopo che Gestione API ha ottenuto il token di autorizzazione, può inviare la richiesta per la convalida del token. Lo standard RFC 7662 definisce questa procedura introspezione e richiede che venga inviata una richiesta POST per un modulo HTML alla risorsa di introspezione. Il modulo HTML deve contenere almeno una coppia chiave/valore con la chiave token. Questa richiesta al server di autorizzazione deve anche essere autenticata, per garantire che i clienti malintenzionati non possano cercare di ottenere token validi.

<send-request mode="new" response-variable-name="tokenstate" timeout="20" ignore-error="true">
  <set-url>https://microsoft-apiappec990ad4c76641c6aea22f566efc5a4e.azurewebsites.net/introspection</set-url>
  <set-method>POST</set-method>
  <set-header name="Authorization" exists-action="override">
    <value>basic dXNlcm5hbWU6cGFzc3dvcmQ=</value>
  </set-header>
  <set-header name="Content-Type" exists-action="override">
    <value>application/x-www-form-urlencoded</value>
  </set-header>
  <set-body>@($"token={(string)context.Variables["token"]}")</set-body>
</send-request>

Verifica della risposta

L'attributo response-variable-name viene usato per concedere l'accesso alla risposta restituita. Il nome definito in questa proprietà può essere usato come chiave nel dizionario context.Variables per accedere all'oggetto IResponse.

Dall'oggetto della risposta è possibile recuperare il corpo e lo standard RFC 7622 indica a Gestione API che la risposta deve essere un oggetto JSON e deve contenere almeno una proprietà denominata active che rappresenta un valore booleano. Quando active è true, il token viene considerato valido.

In alternativa, se il server di autorizzazione non include il "active" campo per indicare se il token è valido, usare uno strumento client HTTP, curl ad esempio per determinare quali proprietà sono impostate in un token valido. Ad esempio, se una risposta di token valida contiene una proprietà denominata "expires_in", verificare se questo nome di proprietà esiste nella risposta del server di autorizzazione in questo modo:

<when condition="@(((IResponse)context.Variables["tokenstate"]).Body.As<JObject>().Property("expires_in") == null)">

Creazione di report sull'errore

Per individuare un token non valido e, in tal caso restituire una risposta 401, è possibile usare i criteri <choose>.

<choose>
  <when condition="@((bool)((IResponse)context.Variables["tokenstate"]).Body.As<JObject>()["active"] == false)">
    <return-response response-variable-name="existing response variable">
      <set-status code="401" reason="Unauthorized" />
      <set-header name="WWW-Authenticate" exists-action="override">
        <value>Bearer error="invalid_token"</value>
      </set-header>
    </return-response>
  </when>
</choose>

Secondo RFC 6750, che descrive come dovrebbero essere usati i token, la gestione delle API restituisce anche un'intestazione bearer con la risposta 401. L'intestazione WWW-Authenticate è progettata per indicare a un client come realizzare una richiesta correttamente autorizzata. A causa dell'ampia gamma di approcci possibili con il framework OAuth2, è difficile comunicare tutte le informazioni necessarie. Fortunatamente, sono disponibili soluzioni che consentono ai client di definire le modalità per autorizzare correttamente le richieste a un server di risorse.

Soluzione finale

Al termine, si ottengono i criteri seguenti:

<inbound>
  <!-- Extract Token from Authorization header parameter -->
  <set-variable name="token" value="@(context.Request.Headers.GetValueOrDefault("Authorization","scheme param").Split(' ').Last())" />

  <!-- Send request to Token Server to validate token (see RFC 7662) -->
  <send-request mode="new" response-variable-name="tokenstate" timeout="20" ignore-error="true">
    <set-url>https://microsoft-apiappec990ad4c76641c6aea22f566efc5a4e.azurewebsites.net/introspection</set-url>
    <set-method>POST</set-method>
    <set-header name="Authorization" exists-action="override">
      <value>basic dXNlcm5hbWU6cGFzc3dvcmQ=</value>
    </set-header>
    <set-header name="Content-Type" exists-action="override">
      <value>application/x-www-form-urlencoded</value>
    </set-header>
    <set-body>@($"token={(string)context.Variables["token"]}")</set-body>
  </send-request>

  <choose>
    <!-- Check active property in response -->
    <when condition="@((bool)((IResponse)context.Variables["tokenstate"]).Body.As<JObject>()["active"] == false)">
      <!-- Return 401 Unauthorized with http-problem payload -->
      <return-response response-variable-name="existing response variable">
        <set-status code="401" reason="Unauthorized" />
        <set-header name="WWW-Authenticate" exists-action="override">
          <value>Bearer error="invalid_token"</value>
        </set-header>
      </return-response>
    </when>
  </choose>
  <base />
</inbound>

Questo esempio è solo uno dei molti che illustrano come i send-request criteri possono essere usati per integrare servizi esterni utili nel processo di richieste e risposte che passano attraverso il servizio Gestione API.

Composizione della risposta

Il send-request criterio può essere usato per migliorare una richiesta primaria a un sistema back-end, come illustrato nell'esempio precedente, oppure può essere usato per sostituire completamente la chiamata back-end. Questa tecnica consente di creare facilmente risorse composite che vengono aggregate da più sistemi.

Creazione di un dashboard

A volte può essere utile saper esporre le informazioni presenti in più sistemi back-end, ad esempio, per creare un dashboard. Gli indicatori di prestazioni chiave (KPI) provengono da tutti i back-end diversi, ma si preferisce non fornire l'accesso diretto a tali indicatori. Comunque, sarebbe bello recuperare tutte le informazioni in una singola richiesta. Ad esempio, può essere innanzitutto necessario sezionare e ripulire alcune delle informazioni di back-end. Essere in grado di memorizzare nella cache tale risorsa composita sarebbe un modo utile per ridurre il carico back-end, come si sa, gli utenti hanno l'abitudine di martellare la chiave F5 per verificare se le metriche di sottoperformazione potrebbero cambiare.

Simulazione della risorsa

Il primo passaggio per la creazione della risorsa dashboard consiste nella configurazione di una nuova operazione nel portale di Azure. Questa operazione segnaposto viene usata per configurare una politica di composizione per costruire la risorsa dinamica.

Screenshot che mostra una nuova operazione dashboard configurata nel portale di Azure.

Invio delle richieste

Dopo aver creato l'operazione, è possibile configurare un criterio specifico per tale operazione.

Screenshot che mostra la schermata dell'Ambito della politica.

Il primo passaggio consiste nell'estrarre eventuali parametri di query dalla richiesta in ingresso, in modo da poterli inoltrare al back-end. In questo esempio il dashboard visualizza informazioni basate su un periodo di tempo e dispone quindi dei parametri fromDate e toDate. È possibile usare i criteri set-variable per estrarre le informazioni dall'URL della richiesta.

<set-variable name="fromDate" value="@(context.Request.Url.Query["fromDate"].Last())">
<set-variable name="toDate" value="@(context.Request.Url.Query["toDate"].Last())">

Quando le informazioni sono disponibili, è possibile inviare richieste a tutti i sistemi back-end. Ogni richiesta crea un nuovo URL con le informazioni sul parametro, chiama il rispettivo server e archivia la risposta in una variabile di contesto.

<send-request mode="new" response-variable-name="revenuedata" timeout="20" ignore-error="true">
  <set-url>@($"https://accounting.acme.com/salesdata?from={(string)context.Variables["fromDate"]}&to={(string)context.Variables["fromDate"]}")</set-url>
  <set-method>GET</set-method>
</send-request>

<send-request mode="new" response-variable-name="materialdata" timeout="20" ignore-error="true">
  <set-url>@($"https://inventory.acme.com/materiallevels?from={(string)context.Variables["fromDate"]}&to={(string)context.Variables["fromDate"]}")</set-url>
  <set-method>GET</set-method>
</send-request>

<send-request mode="new" response-variable-name="throughputdata" timeout="20" ignore-error="true">
  <set-url>@($"https://production.acme.com/throughput?from={(string)context.Variables["fromDate"]}&to={(string)context.Variables["fromDate"]}")</set-url>
  <set-method>GET</set-method>
</send-request>

<send-request mode="new" response-variable-name="accidentdata" timeout="20" ignore-error="true">
  <set-url>@($"https://production.acme.com/accidentdata?from={(string)context.Variables["fromDate"]}&to={(string)context.Variables["fromDate"]}")</set-url>
  <set-method>GET</set-method>
</send-request>

Gestione API invia queste richieste in sequenza.

Invio delle risposte

Per costruire la risposta composita, è possibile usare i criteri return-response. L'elemento set-body può usare un'espressione per creare un nuovo oggetto JObject con tutte le rappresentazioni di componenti incorporate come proprietà.

<return-response response-variable-name="existing response variable">
  <set-status code="200" reason="OK" />
  <set-header name="Content-Type" exists-action="override">
    <value>application/json</value>
  </set-header>
  <set-body>
    @(new JObject(new JProperty("revenuedata",((IResponse)context.Variables["revenuedata"]).Body.As<JObject>()),
                  new JProperty("materialdata",((IResponse)context.Variables["materialdata"]).Body.As<JObject>()),
                  new JProperty("throughputdata",((IResponse)context.Variables["throughputdata"]).Body.As<JObject>()),
                  new JProperty("accidentdata",((IResponse)context.Variables["accidentdata"]).Body.As<JObject>())
                  ).ToString())
  </set-body>
</return-response>

I criteri completi saranno simili ai seguenti:

<policies>
  <inbound>
    <set-variable name="fromDate" value="@(context.Request.Url.Query["fromDate"].Last())">
    <set-variable name="toDate" value="@(context.Request.Url.Query["toDate"].Last())">

    <send-request mode="new" response-variable-name="revenuedata" timeout="20" ignore-error="true">
      <set-url>@($"https://accounting.acme.com/salesdata?from={(string)context.Variables["fromDate"]}&to={(string)context.Variables["fromDate"]}")"</set-url>
      <set-method>GET</set-method>
    </send-request>

    <send-request mode="new" response-variable-name="materialdata" timeout="20" ignore-error="true">
      <set-url>@($"https://inventory.acme.com/materiallevels?from={(string)context.Variables["fromDate"]}&to={(string)context.Variables["fromDate"]}")"</set-url>
      <set-method>GET</set-method>
    </send-request>

    <send-request mode="new" response-variable-name="throughputdata" timeout="20" ignore-error="true">
      <set-url>@($"https://production.acme.com/throughput?from={(string)context.Variables["fromDate"]}&to={(string)context.Variables["fromDate"]}")"</set-url>
      <set-method>GET</set-method>
    </send-request>

    <send-request mode="new" response-variable-name="accidentdata" timeout="20" ignore-error="true">
      <set-url>@($"https://production.acme.com/accidentdata?from={(string)context.Variables["fromDate"]}&to={(string)context.Variables["fromDate"]}")"</set-url>
      <set-method>GET</set-method>
    </send-request>

    <return-response response-variable-name="existing response variable">
      <set-status code="200" reason="OK" />
      <set-header name="Content-Type" exists-action="override">
        <value>application/json</value>
      </set-header>
      <set-body>
        @(new JObject(new JProperty("revenuedata",((IResponse)context.Variables["revenuedata"]).Body.As<JObject>()),
                      new JProperty("materialdata",((IResponse)context.Variables["materialdata"]).Body.As<JObject>()),
                      new JProperty("throughputdata",((IResponse)context.Variables["throughputdata"]).Body.As<JObject>()),
                      new JProperty("accidentdata",((IResponse)context.Variables["accidentdata"]).Body.As<JObject>())
        ).ToString())
      </set-body>
    </return-response>
  </inbound>
  <backend>
    <base />
  </backend>
  <outbound>
    <base />
  </outbound>
</policies>

Summary

Il servizio Gestione API di Azure offre criteri flessibili che possono essere applicati in modo selettivo al traffico HTTP e consentono la realizzazione di servizi back-end. Se si desidera migliorare il gateway API con funzioni di avviso, verifica e convalida o creare nuove risorse complesse basate su più servizi back-end, send-request e i criteri correlati offrono numerose possibilità.