Dela via


Använda externa tjänster från Azure API Management-tjänsten

GÄLLER FÖR: Alla API Management-nivåer

De principer som är tillgängliga i Azure API Management-tjänsten kan utföra ett brett utbud av användbart arbete baserat enbart på inkommande begäran, utgående svar och grundläggande konfigurationsinformation. Men att kunna interagera med externa tjänster från API Management-principer öppnar många fler möjligheter.

Du har tidigare sett hur du interagerar med Azure Event Hub-tjänsten för loggning, övervakning och analys. Den här artikeln visar principer som gör att du kan interagera med alla externa HTTP-baserade tjänster. Dessa principer kan användas för att utlösa fjärrhändelser eller för att hämta information som används för att ändra den ursprungliga begäran och det ursprungliga svaret på något sätt.

Send-One-Way-Request

Möjligen är den enklaste externa interaktionen typen av begäran som gör att en extern tjänst kan meddelas om någon form av viktig händelse. Kontrollflödesprincipen choose kan användas för att identifiera alla typer av villkor som du är intresserad av. Om villkoret är uppfyllt kan du göra en extern HTTP-begäran med hjälp av principen send-one-way-request . Detta kan vara en begäran till ett meddelandesystem som Hipchat eller Slack, eller ett e-post-API som SendGrid eller MailChimp, eller för kritiska supportincidenter som PagerDuty. Alla dessa meddelandesystem har enkla HTTP-API:er som kan anropas.

Aviseringar med Slack

I följande exempel visas hur du skickar ett meddelande till ett Slack-chattrum om HTTP-svarsstatuskoden är större än eller lika med 500. Ett 500-intervallfel anger ett problem med serverdels-API:et som klienten för API:et inte kan lösa själva. Det kräver vanligtvis någon form av åtgärder i API Management-delen.

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

Slack har begreppet inkommande webbkrokar. När du konfigurerar en inkommande webbkrok genererar Slack en särskild URL som gör att du kan göra en enkel POST-begäran och skicka ett meddelande till Slack-kanalen. JSON-brödtexten som du skapar baseras på ett format som definierats av Slack.

Slack Web Hook

Är eld och glöm tillräckligt bra?

Det finns vissa kompromisser när du använder en fire-and-forget-typ av begäran. Om begäran av någon anledning misslyckas rapporteras inte felet. I den här situationen är det inte motiverat att ha ett sekundärt system för rapportering av fel och den extra prestandakostnaden för att vänta på svaret. För scenarier där det är viktigt att kontrollera svaret är principen för att skicka begäran ett bättre alternativ.

Skicka begäran

Principen send-request gör det möjligt att använda en extern tjänst för att utföra komplexa bearbetningsfunktioner och returnera data till API-hanteringstjänsten som kan användas för ytterligare principbearbetning.

Auktorisera referenstoken

En viktig funktion i API Management är att skydda serverdelsresurser. Om auktoriseringsservern som används av ditt API skapar JWT-token som en del av dess OAuth2-flöde, som Microsoft Entra-ID gör, kan du använda validate-jwt principen eller validate-azure-ad-token principen för att verifiera tokens giltighet. Vissa auktoriseringsservrar skapar det som kallas referenstoken som inte kan verifieras utan att göra ett återanrop till auktoriseringsservern.

Standardiserad introspektion

Tidigare har det inte funnits något standardiserat sätt att verifiera en referenstoken med en auktoriseringsserver. Men en nyligen föreslagen standard RFC 7662 publicerades av IETF som definierar hur en resursserver kan verifiera giltigheten för en token.

Extrahera token

Det första steget är att extrahera token från auktoriseringshuvudet. Rubrikvärdet ska formateras med Bearer auktoriseringsschemat, ett enda blanksteg och sedan auktoriseringstoken enligt RFC 6750. Tyvärr finns det fall där auktoriseringsschemat utelämnas. För att ta hänsyn till detta vid parsning delar API Management upp rubrikvärdet på ett blanksteg och väljer den sista strängen från den returnerade matrisen med strängar. Detta ger en lösning för felaktigt formaterade auktoriseringshuvuden.

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

Göra valideringsbegäran

När API Management har auktoriseringstoken kan API Management göra begäran för att verifiera token. RFC 7662 anropar den här processens introspektion och kräver att du POST har ett HTML-formulär till introspektionsresursen. HTML-formuläret måste minst innehålla ett nyckel/värde-par med nyckeln token. Den här begäran till auktoriseringsservern måste också autentiseras för att säkerställa att skadliga klienter inte kan tråla efter giltiga token.

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

Kontrollera svaret

Attributet response-variable-name används för att ge åtkomst till det returnerade svaret. Namnet som definieras i den här egenskapen kan användas som en nyckel i context.Variables ordlistan för att komma åt IResponse objektet.

Från svarsobjektet kan du hämta brödtexten och RFC 7622 meddelar API Management att svaret måste vara ett JSON-objekt och måste innehålla minst en egenskap som heter active som är ett booleskt värde. När active är sant anses token vara giltig.

Om auktoriseringsservern inte innehåller fältet "aktiv" för att ange om token är giltig använder du ett verktyg som Postman för att avgöra vilka egenskaper som anges i en giltig token. Om ett giltigt tokensvar till exempel innehåller en egenskap med namnet "expires_in" kontrollerar du om det här egenskapsnamnet finns i auktoriseringsserverns svar på det här sättet:

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

Rapporteringsfel

Du kan använda en <choose> princip för att identifiera om token är ogiltig och i så fall returnera ett 401-svar.

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

Enligt RFC 6750 som beskriver hur bearer token ska användas, returnerar API Management även en WWW-Authenticate rubrik med 401-svaret. WWW-Authenticate är avsedd att instruera en klient om hur du skapar en korrekt auktoriserad begäran. På grund av de många olika metoder som är möjliga med OAuth2-ramverket är det svårt att kommunicera all nödvändig information. Lyckligtvis pågår det arbete för att hjälpa klienter att identifiera hur begäranden kan auktoriseras korrekt till en resursserver.

Slutlig lösning

I slutet får du följande princip:

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

Detta är bara ett av många exempel på hur send-request principen kan användas för att integrera användbara externa tjänster i processen för begäranden och svar som flödar via API Management-tjänsten.

Svarssammansättning

Principen send-request kan användas för att förbättra en primär begäran till ett serverdelssystem, som du såg i föregående exempel, eller så kan den användas som en fullständig ersättning för serverdelsanropet. Med den här tekniken kan du enkelt skapa sammansatta resurser som aggregeras från flera olika system.

Skapa en instrumentpanel

Ibland vill du kunna exponera information som finns i flera serverdelssystem, till exempel för att köra en instrumentpanel. KPI:er kommer från alla olika serverdelar, men du föredrar att inte ge direkt åtkomst till dem och det skulle vara trevligt om all information kunde hämtas i en enda begäran. Kanske behöver en del av serverdelsinformationen lite skärning och diktering och lite sanering först! Att kunna cachelagra den sammansatta resursen skulle vara användbart för att minska serverdelsbelastningen eftersom du vet att användarna har för vana att hamra F5-nyckeln för att se om deras underpresterande mått kan ändras.

Fejka resursen

Det första steget för att skapa instrumentpanelsresursen är att konfigurera en ny åtgärd i Azure-portalen. Det här är en platshållaråtgärd som används för att konfigurera en sammansättningsprincip för att skapa den dynamiska resursen.

Instrumentpanelsåtgärd

Göra begäranden

När åtgärden har skapats kan du konfigurera en princip specifikt för den åtgärden.

Skärmbild som visar skärmen Principomfång.

Det första steget är att extrahera alla frågeparametrar från den inkommande begäran så att du kan vidarebefordra dem till serverdelen. I det här exemplet visar instrumentpanelen information baserat på en tidsperiod och har därför en och toDate -fromDateparameter. Du kan använda set-variable principen för att extrahera informationen från begärande-URL:en.

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

När du har den här informationen kan du göra begäranden till alla serverdelssystem. Varje begäran skapar en ny URL med parameterinformationen och anropar respektive server och lagrar svaret i en kontextvariabel.

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

API Management skickar dessa begäranden sekventiellt.

Svara

Om du vill skapa det sammansatta svaret kan du använda principen return-response . Elementet set-body kan använda ett uttryck för att konstruera ett nytt JObject med alla komponentrepresentationer inbäddade som egenskaper.

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

Den fullständiga principen ser ut så här:

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

Sammanfattning

Azure API Management-tjänsten tillhandahåller flexibla principer som kan tillämpas selektivt på HTTP-trafik och möjliggör sammansättning av serverdelstjänster. Oavsett om du vill förbättra din API-gateway med aviseringsfunktioner, verifierings-, valideringsfunktioner eller skapa nya sammansatta resurser baserat på flera serverdelstjänster, öppnar och send-request relaterade principer en värld av möjligheter.