Usando serviços externos do serviço de Gerenciamento de API do Azure

APLICA-SE A: Todas as camadas de gerenciamento de API

As políticas disponíveis no serviço de Gerenciamento de API do Azure podem fazer uma ampla gama de trabalho útil com base exclusivamente na solicitação de entrada, na resposta de saída e nas informações básicas de configuração. No entanto, ser capaz de interagir com serviços externos a partir de políticas de gerenciamento de API abre muito mais oportunidades.

Você já viu como interagir com o serviço Hub de Eventos do Azure para registro, monitoramento e análise. Este artigo demonstra políticas que permitem interagir com qualquer serviço externo baseado em HTTP. Essas políticas podem ser usadas para acionar eventos remotos ou para recuperar informações usadas para manipular a solicitação e a resposta originais de alguma forma.

Solicitação de envio unidirecional

Possivelmente, a interação externa mais simples é o estilo de solicitação de fogo e esquecimento que permite que um serviço externo seja notificado de algum tipo de evento importante. A política choose de fluxo de controle pode ser usada para detetar qualquer tipo de condição em que você esteja interessado. Se a condição for satisfeita, você poderá fazer uma solicitação HTTP externa usando a política de solicitação de envio unidirecional. Isso pode ser uma solicitação para um sistema de mensagens como Hipchat ou Slack, ou uma API de e-mail como SendGrid ou MailChimp, ou para incidentes de suporte críticos algo como PagerDuty. Todos esses sistemas de mensagens têm APIs HTTP simples que podem ser invocadas.

Alertando com o Slack

O exemplo a seguir demonstra como enviar uma mensagem para uma sala de chat do Slack se o código de status da resposta HTTP for maior ou igual a 500. Um erro de intervalo 500 indica um problema com a API de back-end que o cliente da API não pode resolver sozinho. Geralmente requer algum tipo de intervenção na parte de Gerenciamento de 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>

O Slack tem a noção de ganchos web de entrada. Ao configurar um gancho da Web de entrada, o Slack gera uma URL especial, que permite que você faça uma solicitação POST simples e passe uma mensagem para o canal do Slack. O corpo JSON que você cria é baseado em um formato definido pelo Slack.

Gancho Web Slack

Fogo e esquecimento são bons o suficiente?

Há certas compensações ao usar um estilo de solicitação de fogo e esquecimento. Se, por algum motivo, a solicitação falhar, a falha não será relatada. Nesta situação específica, a complexidade de ter um sistema secundário de comunicação de falhas e o custo adicional de desempenho de esperar pela resposta não são justificados. Para cenários em que é essencial verificar a resposta, a política de solicitação de envio é uma opção melhor.

Pedido-Envio

A send-request política permite usar um serviço externo para executar funções de processamento complexas e retornar dados para o serviço de gerenciamento de API que podem ser usados para processamento adicional de políticas.

Autorizando tokens de referência

Uma das principais funções do Gerenciamento de API é proteger os recursos de back-end. Se o servidor de autorização usado pela API criar tokens JWT como parte de seu fluxo OAuth2, como faz o Microsoft Entra ID , você poderá usar a validate-jwt política ou validate-azure-ad-token a política para verificar a validade do token. Alguns servidores de autorização criam os chamados tokens de referência que não podem ser verificados sem fazer um retorno de chamada para o servidor de autorização.

Introspeção padronizada

No passado, não havia uma maneira padronizada de verificar um token de referência com um servidor de autorização. No entanto, um padrão recentemente proposto RFC 7662 foi publicado pela IETF que define como um servidor de recursos pode verificar a validade de um token.

Extraindo o token

A primeira etapa é extrair o token do cabeçalho Authorization. O valor do cabeçalho deve ser formatado com o esquema de Bearer autorização, um único espaço e, em seguida, o token de autorização de acordo com a RFC 6750. Infelizmente, há casos em que o regime de autorização é omitido. Para levar isso em conta ao analisar, o Gerenciamento de API divide o valor do cabeçalho em um espaço e seleciona a última cadeia de caracteres da matriz retornada de cadeias de caracteres. Isso fornece uma solução alternativa para cabeçalhos de autorização mal formatados.

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

Fazer o pedido de validação

Depois que o Gerenciamento de API tiver o token de autorização, o Gerenciamento de API poderá fazer a solicitação para validar o token. RFC 7662 chama esse processo de introspeção e requer que você POST um formulário HTML para o recurso de introspeção. O formulário HTML deve conter pelo menos um par chave/valor com a chave token. Essa solicitação ao servidor de autorização também deve ser autenticada, para garantir que clientes mal-intencionados não possam procurar tokens válidos.

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

Verificar a resposta

O response-variable-name atributo é usado para dar acesso à resposta retornada. O nome definido nesta propriedade pode ser usado como uma chave no context.Variables dicionário para acessar o IResponse objeto.

A partir do objeto de resposta, você pode recuperar o corpo e o RFC 7622 informa ao Gerenciamento de API que a resposta deve ser um objeto JSON e deve conter pelo menos uma propriedade chamada active que é um valor booleano. Quando active é verdadeiro, então o token é considerado válido.

Como alternativa, se o servidor de autorização não incluir o campo "ativo" para indicar se o token é válido, use uma ferramenta como o Postman para determinar quais propriedades estão definidas em um token válido. Por exemplo, se uma resposta de token válida contiver uma propriedade chamada "expires_in", verifique se esse nome de propriedade existe na resposta do servidor de autorização desta forma:

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

Comunicação de falhas

Você pode usar uma <choose> política para detetar se o token é inválido e, em caso afirmativo, retornar uma resposta 401.

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

De acordo com a RFC 6750 , que descreve como bearer os tokens devem ser usados, o Gerenciamento de API também retorna um WWW-Authenticate cabeçalho com a resposta 401. O WWW-Authenticate destina-se a instruir um cliente sobre como construir uma solicitação devidamente autorizada. Devido à grande variedade de abordagens possíveis com a estrutura OAuth2, é difícil comunicar todas as informações necessárias. Felizmente, há esforços em andamento para ajudar os clientes a descobrir como autorizar corretamente as solicitações para um servidor de recursos.

Solução final

No final, você obtém a seguinte política:

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

Este é apenas um dos muitos exemplos de como a send-request política pode ser usada para integrar serviços externos úteis no processo de solicitações e respostas que fluem através do serviço de Gerenciamento de API.

Composição da resposta

A send-request política pode ser usada para aprimorar uma solicitação primária para um sistema de back-end, como você viu no exemplo anterior, ou pode ser usada como uma substituição completa para a chamada de back-end. Usando essa técnica, você pode facilmente criar recursos compostos que são agregados a partir de vários sistemas diferentes.

Criando um painel

Às vezes, você quer ser capaz de expor informações que existem em vários sistemas de back-end, por exemplo, para dirigir um painel. Os KPIs vêm de todos os diferentes back-ends, mas você preferiria não fornecer acesso direto a eles e seria bom se todas as informações pudessem ser recuperadas em uma única solicitação. Talvez algumas das informações de back-end precisem de algum corte e corte e um pouco de higienização primeiro! Ser capaz de armazenar em cache esse recurso composto seria útil para reduzir a carga de back-end, pois você sabe que os usuários têm o hábito de martelar a tecla F5 para ver se suas métricas de baixo desempenho podem mudar.

Falsificar o recurso

A primeira etapa para criar o recurso de painel é configurar uma nova operação no portal do Azure. Esta é uma operação de espaço reservado usada para configurar uma política de composição para criar o recurso dinâmico.

Operação do painel

Fazer os pedidos

Depois que a operação tiver sido criada, você poderá configurar uma política especificamente para essa operação.

Captura de tela que mostra a tela Escopo da política.

A primeira etapa é extrair quaisquer parâmetros de consulta da solicitação de entrada, para que você possa encaminhá-los para o back-end. Neste exemplo, o painel está mostrando informações com base em um período de tempo e, portanto, tem um fromDate parâmetro e toDate . Você pode usar a set-variable política para extrair as informações da URL da solicitação.

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

Depois de ter essas informações, você pode fazer solicitações para todos os sistemas de back-end. Cada solicitação constrói uma nova URL com as informações do parâmetro e chama seu respetivo servidor e armazena a resposta em uma variável de contexto.

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

O Gerenciamento de API enviará essas solicitações sequencialmente.

Resposta

Para construir a resposta composta, você pode usar a política de retorno-resposta . O set-body elemento pode usar uma expressão para construir um novo JObject com todas as representações de componentes incorporadas como propriedades.

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

A política completa tem a seguinte aparência:

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

Resumo

O serviço de Gerenciamento de API do Azure fornece políticas flexíveis que podem ser aplicadas seletivamente ao tráfego HTTP e permite a composição de serviços de back-end. Se você deseja aprimorar seu gateway de API com funções de alerta, verificação, recursos de validação ou criar novos recursos compostos com base em vários serviços de back-end, as send-request políticas relacionadas abrem um mundo de possibilidades.