Compartilhar via


Uso dos serviços externos do serviço de Gerenciamento de API do Azure

APLICA-SE A: todas as camadas do Gerenciamento de API

As políticas disponíveis no serviço de Gerenciamento de API do Azure permitem uma ampla variedade de trabalhos úteis com base apenas na solicitação de entrada, na resposta de saída e em informações básicas de configuração. No entanto, a capacidade de interagir com serviços externos das políticas de Gerenciamento de API abre muitas outras oportunidades.

Em artigos anteriores, você viu como interagir com o serviço hubs de eventos do Azure para registro em log, 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 disparar eventos remotos ou para recuperar informações que são usadas para manipular a solicitação e resposta originais de alguma forma.

Send-One-Way-Request

Possivelmente, a interação externa mais simples é o estilo de solicitação fire-and-forget 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 detectar qualquer tipo de condição em que você esteja interessado. Se as condições forem atendidas, será possível fazer uma solicitação HTTP externa usando a política send-one-way-request. Essa solicitação pode ser para um sistema de mensagens como Hipchat ou Slack, ou uma API de email como SendGrid ou MailChimp, ou para incidentes de suporte crítico, algo como PagerDuty. Todos esses sistemas de mensagens possuem APIs HTTP simples que podem ser invocadas.

Alertas com Slack

O exemplo a seguir demonstra como enviar uma mensagem a uma sala de bate-papo do Slack, se o código de status de resposta HTTP for maior ou igual a 500. Um erro de intervalo de 500 indica um problema com a API de back-end que o cliente da API não pode resolver sozinho. Geralmente, isso requer algum tipo de intervenção por parte do 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 de entrada da Web. Ao configurar um web hook de entrada, o Slack gera uma URL exclusiva, que permite fazer uma solicitação POST básica e transmitir uma mensagem ao canal Slack. O corpo JSON que você cria é baseado em um formato definido pelo Slack.

Captura de tela mostrando um Webhook do Slack.

O método "disparar e esquecer" é o suficiente?

Há certas compensações ao usar um estilo de solicitação "disparar e esquecer". Se, por algum motivo, a solicitação falhar, a falha não será relatada. Nessa situação, a complexidade de um sistema de relatório de falhas secundário e o custo adicional de desempenho ao aguardar a resposta não se justifica. Para cenários em que é essencial verificar a resposta, a política de solicitação de envio é uma opção melhor.

send-request

A política send-request permite o uso de um serviço externo para executar funções complexas de processamento e retornar dados para o serviço de gerenciamento de API, que pode ser usado para um processamento adicional da política.

Autorizando tokens de referência

Uma função importante do Gerenciamento de API é proteger os recursos de back-end. Se o servidor de autorização usado pela sua API criar JSON Web Tokens (JWTs) como parte do fluxo OAuth2, assim como faz o Microsoft Entra ID, então você pode usar a política validate-jwt ou a política validate-azure-ad-token para verificar a validade do token. Alguns servidores de autorização criam tokens de referência, conhecidos como tokens de referência, que não podem ser verificados sem fazer um retorno de chamada para o servidor de autorização.

Introspecçã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, o IETF (Internet Engineering Task Force) publicou recentemente o RFC 7662 padrão proposto que define como um servidor de recursos pode verificar a validade de um token.

Extração do token

A primeira etapa é extrair o token do cabeçalho de Autorização. O valor do cabeçalho deverá ser formatado com o esquema de autorização Bearer, um espaço simples e, em seguida, o token de autorização de acordo com a RFC 6750. Infelizmente, há casos nos quais o esquema de autorização é omitido. Para considerar essa omissão ao analisar, o Gerenciamento de API divide o valor do cabeçalho em um espaço e seleciona a última cadeia de caracteres na matriz retornada de cadeias de caracteres. Esse método 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())" />

Realização da solicitação de validação

Quando o Gerenciamento de API tiver o token de autorização, o Gerenciamento de API poderá fazer a solicitação para validar o token. A RFC 7662 chama esse processo de introspecção e exige que você aplique POST a um formulário HTML para o recurso de introspecção. O formulário HTML deve conter pelo menos um par de chave/valor com a chave token. Esta 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>

Verificação da resposta

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

A partir do objeto de resposta, é possível recuperar o corpo e a RFC 7622 informa Gerenciamento de API que a resposta deve ser um objeto JSON e conter pelo menos uma propriedade chamada active que seja um valor booliano. Quando active for verdadeiro, o token é considerado válido.

Como alternativa, se o servidor de autorização não incluir o campo "active" para indicar se o token é válido, use uma ferramenta de cliente HTTP, como curl, para determinar quais propriedades sã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 dessa maneira:

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

Indicação de falha

É possível usar <choose> para detectar 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>

Conforme RFC 6750, que descreve como os tokens bearer devem ser usados, o API Management também retorna um cabeçalho WWW-Authenticate com a resposta 401. O WWW-Authenticate tem como intenção instruir um cliente sobre como construir uma solicitação devidamente autorizada. Devido à ampla variedade de abordagens possíveis com a estrutura OAuth2, é difícil comunicar todas as informações necessárias. Felizmente, há esforços sendo realizados para ajudar os clientes a descobrirem 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 exemplo é apenas um dos muitos que demonstram como a send-request política pode ser usada para integrar serviços externos úteis ao processo de solicitações e respostas que fluem por meio 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 para substituir completamente a chamada de back-end. Usando essa técnica, é possível criar facilmente recursos compostos que são agregados de vários sistemas diferentes.

Criando um painel

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

Falsificando o recurso

A primeira etapa para a construção do recurso do painel é configurar uma nova operação no Portal do Azure. Esta operação de placeholder é usada para configurar uma política de composição a fim de criar o recurso dinâmico.

Captura de tela mostrando uma nova operação do Painel sendo configurada no portal do Azure.

Fazendo as solicitações

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

Captura de tela que mostra a tela do escopo da Política.

A primeira etapa é extrair os parâmetros de consulta da solicitação de entrada, para que você possa encaminhá-los ao back-end. Nesse exemplo, o painel exibe informações baseadas em um período de tempo e, portanto, tem um parâmetro fromDate e toDate. É possível usar set-variable 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())">

Assim que tiver essas informações, você poderá fazer solicitações a todos os sistemas back-end. Cada solicitação constrói uma nova URL com as informações de parâmetro, chama seu respectivo 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 envia essas solicitações sequencialmente.

Respondendo

Para construir a resposta composta, é possível usar a política return-response. O elemento set-body pode usar uma expressão para construir um novo JObject com todas as representações de componente 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 back-end. Se você quiser aprimorar seu gateway de API com funções de alerta, verificação e recursos de validação, ou criar novos recursos compostos baseados em vários serviços back-end, a política send-request e as políticas relacionadas abrirão um mundo de oportunidades.