HTTP Client
HTTP ClientHTTP Client

HTTP Client

Included in the “Power Extensions” bundle

Hinzufügen von Feldern zum GraphQL-Schema, um HTTP-Anfragen an einen Webserver zu senden und deren Antwort abzurufen:

  • _sendJSONObjectItemHTTPRequest
  • _sendJSONObjectItemHTTPRequests
  • _sendJSONObjectCollectionHTTPRequest
  • _sendJSONObjectCollectionHTTPRequests
  • _sendHTTPRequest
  • _sendHTTPRequests
  • _sendGraphQLHTTPRequest
  • _sendGraphQLHTTPRequests

Aus Sicherheitsgründen müssen die URLs, zu denen eine Verbindung hergestellt werden darf, explizit konfiguriert werden.

Feldliste

Die folgenden Felder werden dem Schema hinzugefügt.

_sendJSONObjectItemHTTPRequest

Ruft die (REST-)Antwort für ein einzelnes JSON-Objekt ab.

Signature: _sendJSONObjectItemHTTPRequest(input: HTTPRequestInput!): JSONObject.

_sendJSONObjectItemHTTPRequests

Ruft die (REST-)Antwort für ein einzelnes JSON-Objekt von mehreren Endpunkten ab, entweder asynchron (parallel) oder synchron (nacheinander) ausgeführt.

Signature: _sendJSONObjectItemHTTPRequests(async: Boolean = true, inputs: [HTTPRequestInput!]!): [JSONObject].

_sendJSONObjectCollectionHTTPRequest

Ruft die (REST-)Antwort für eine Sammlung von JSON-Objekten ab.

Signature: _sendJSONObjectCollectionHTTPRequest(input: HTTPRequestInput!): [JSONObject].

_sendJSONObjectCollectionHTTPRequests

Ruft die (REST-)Antwort für eine Sammlung von JSON-Objekten von mehreren Endpunkten ab, entweder asynchron (parallel) oder synchron (nacheinander) ausgeführt.

Signature: _sendJSONObjectCollectionHTTPRequests(async: Boolean = true, inputs: [HTTPRequestInput!]!): [[JSONObject]].

_sendHTTPRequest

Stellt eine Verbindung zur angegebenen URL her und ruft ein HTTPResponse-Objekt ab, das die folgenden Felder enthält:

  • statusCode: Int!
  • contentType: String!
  • body: String!
  • headers: JSONObject!
  • header(name: String!): String
  • hasHeader(name: String!): Boolean!

Signature: _sendHTTPRequest(input: HTTPRequestInput!): HTTPResponse.

_sendHTTPRequests

Ähnlich wie _sendHTTPRequest, empfängt aber mehrere URLs und ermöglicht eine asynchrone (parallele) Verbindung zu diesen.

Signature: _sendHTTPRequests(async: Boolean = true, inputs: [HTTPRequestInput!]!): [HTTPResponse].

_sendGraphQLHTTPRequest

Führt eine GraphQL-Query gegen den angegebenen Endpunkt aus und ruft die Antwort als JSON-Objekt ab.

Der Input dieses Feldes akzeptiert die für GraphQL erwarteten Daten: den Endpunkt, die GraphQL-Query, Variablen und den Operationsnamen, und setzt bereits die Standardmethode (POST) und den Content Type (application/json).

Signature: _sendGraphQLHTTPRequest(input: GraphQLRequestInput!): JSONObject.

_sendGraphQLHTTPRequests

Ähnlich wie _sendGraphQLHTTPRequests, führt aber mehrere GraphQL-Queries gleichzeitig aus, entweder asynchron (parallel) oder synchron (nacheinander).

Signature: _sendGraphQLHTTPRequests(async: Boolean = true, inputs: [GraphQLRequestInput!]!): JSONObject.

Konfiguration der erlaubten URLs

Du musst die Liste der URLs konfigurieren, zu denen eine Verbindung hergestellt werden darf.

Jeder Eintrag kann entweder sein:

  • Ein Regex (regulärer Ausdruck), wenn er von / oder # umschlossen ist, oder
  • Die vollständige URL, andernfalls

Zum Beispiel entspricht jeder dieser Einträge der URL "https://gatographql.com/recipes/":

  • https://gatographql.com/recipes/
  • #https://gatographql.com/recipes/?#
  • #https://gatographql.com/.*#
  • /https:\\/\\/gatographql.com\\/(\S+)/

Es gibt 2 Stellen, an denen diese Konfiguration vorgenommen werden kann, in Reihenfolge der Priorität:

  1. Benutzerdefiniert: In der entsprechenden Schema-Konfiguration
  2. Allgemein: Auf der Einstellungsseite

Wähle in der auf den Endpunkt angewendeten Schema-Konfiguration die Option "Use custom configuration" und gib dann die gewünschten Einträge ein:

Einträge für die Schema-Konfiguration festlegen

Andernfalls werden die in der Registerkarte „Send HTTP Request Fields" der Einstellungen definierten Einträge verwendet:

Einträge in den Einstellungen festlegen
Einträge in den Einstellungen festlegen

Es gibt 2 Verhaltensweisen, „Allow access" und „Deny access":

  • Allow access: Nur die konfigurierten Einträge sind zugänglich, alle anderen nicht
  • Deny access: Die konfigurierten Einträge sind nicht zugänglich, alle anderen schon
Zugriffsverhalten festlegen
Zugriffsverhalten festlegen

Erforderliche Berechtigung für den Zugriff auf interne URLs

Einige URLs werden zu internen Adressen aufgelöst (127.0.0.1, Link-Local-Bereiche, Cloud-Metadata-Endpunkte usw.), die interne Dienste offenlegen können, wenn sie erreicht werden. Diese Einstellung wird auf der Einstellungsseite unter Plugin Configuration > HTTP Client konfiguriert.

Erforderliche Berechtigung für den Zugriff auf interne URLs festlegen
Erforderliche Berechtigung für den Zugriff auf interne URLs festlegen

WordPress-Berechtigung, die der anfragende Benutzer haben muss, um URLs anzusprechen, die zu internen Adressen aufgelöst werden (127.0.0.1, Link-Local-Bereiche, Cloud-Metadata-Endpunkte usw.).

Standardmäßig manage_options, damit Nicht-Admin-Benutzer über die HTTP-Client-Felder keine internen Dienste erreichen können.

Wähle (beliebiger eingeloggter Benutzer), um die Berechtigungsprüfung zu deaktivieren.

Wann welches Feld verwenden

Alle Felder sind ähnlich, aber unterschiedlich.

_sendJSONObjectItemHTTPRequest

Dieses Feld ruft ein JSON-Objekt-Element ab, was nützlich ist, wenn ein einzelnes Element von einem REST-Endpunkt abgefragt wird, z. B. vom WP REST API-Endpunkt /wp-json/wp/v2/posts/1/.

Diese Query:

{
  postData: _sendJSONObjectItemHTTPRequest(input: { url: "https://newapi.getpop.org/wp-json/wp/v2/posts/1/" } )
}

...ruft diese Antwort ab:

{
  "data": {
    "postData": {
      "id": 1,
      "date": "2019-08-02T07:53:57",
      "date_gmt": "2019-08-02T07:53:57",
      "guid": {
        "rendered": "https:\/\/newapi.getpop.org\/?p=1"
      },
      "modified": "2021-01-14T13:18:39",
      "modified_gmt": "2021-01-14T13:18:39",
      "slug": "hello-world",
      "status": "publish",
      "type": "post",
      "link": "https:\/\/newapi.getpop.org\/uncategorized\/hello-world\/",
      "title": {
        "rendered": "Hello world!"
      },
      "content": {
        "rendered": "\n<p>Welcome to WordPress. This is your first post. Edit or delete it, then start writing!<\/p>\n\n\n\n<p>I&#8217;m demonstrating a Youtube video:<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Introduction to the Component-based API by Leonardo Losoviz | JSConf.Asia 2019\" width=\"750\" height=\"422\" src=\"https:\/\/www.youtube.com\/embed\/9pT-q0SSYow?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe>\n<\/div><figcaption>This is my presentation in JSConf Asia 2019<\/figcaption><\/figure>\n",
        "protected": false
      },
      "excerpt": {
        "rendered": "<p>Welcome to WordPress. This is your first post. Edit or delete it, then start writing! I&#8217;m demonstrating a Youtube video:<\/p>\n",
        "protected": false
      },
      "author": 1,
      "featured_media": 0,
      "comment_status": "closed",
      "ping_status": "open",
      "sticky": false,
      "template": "",
      "format": "standard",
      "meta": [],
      "categories": [
        1
      ],
      "tags": [
        193,
        173
      ]
    }
  }
}

_sendJSONObjectCollectionHTTPRequest

Dieses Feld ist ähnlich wie _sendJSONObjectItemHTTPRequest, ruft aber eine Sammlung von JSON-Objekten ab, z. B. vom WP REST API-Endpunkt /wp-json/wp/v2/posts/.

Diese Query:

{
  postData: _sendJSONObjectItemHTTPRequest(input: { url: "https://newapi.getpop.org/wp-json/wp/v2/posts/?per_page=3&_fields=id,type,title,date" } )
}

...ruft diese Antwort ab:

{
  "data": {
    "postData": [
      {
        "id": 1692,
        "date": "2022-04-26T10:10:08",
        "type": "post",
        "title": {
          "rendered": "My Blogroll"
        }
      },
      {
        "id": 1657,
        "date": "2020-12-21T08:24:18",
        "type": "post",
        "title": {
          "rendered": "A tale of two cities &#8211; teaser"
        }
      },
      {
        "id": 1499,
        "date": "2019-08-08T02:49:36",
        "type": "post",
        "title": {
          "rendered": "COPE with WordPress: Post demo containing plenty of blocks"
        }
      }
    ]
  }
}

_sendHTTPRequest

Dieses Feld ruft ein HTTPResponse-Objekt mit allen Eigenschaften der Antwort ab, sodass du unabhängig den Body (der vom Typ String ist, d. h. nicht als JSON umgewandelt wird), den Statuscode, den Content Type und die Header abfragen kannst.

Zum Beispiel liefert die folgende Query:

{
  _sendHTTPRequest(
    input: {
      url: "https://newapi.getpop.org/wp-json/wp/v2/comments/11/?_fields=id,date,content"
    }
  ) {
    statusCode
    contentType
    headers
    body
    contentLengthHeader: header(name: "Content-Length")
    cacheControlHeader: header(name: "Cache-Control")
  }
}

...diese Antwort:

{
  "data": {
    "_sendHTTPRequest": {
      "statusCode": 200,
      "contentType": "application\/json; charset=UTF-8",
      "headers": {
        "Access-Control-Allow-Headers": "Authorization, X-WP-Nonce, Content-Disposition, Content-MD5, Content-Type",
        "Access-Control-Expose-Headers": "X-WP-Total, X-WP-TotalPages, Link",
        "Allow": "GET",
        "Cache-Control": "max-age=300,no-store",
        "Content-Length": "508"
      },
      "body": "{\"id\":11,\"date\":\"2020-12-12T04:09:36\",\"content\":{\"rendered\":\"<p>Wow, this sounds awesome!<\\\/p>\\n\"},\"_links\":{\"self\":[{\"href\":\"https:\\\/\\\/newapi.getpop.org\\\/wp-json\\\/wp\\\/v2\\\/comments\\\/11\"}],\"collection\":[{\"href\":\"https:\\\/\\\/newapi.getpop.org\\\/wp-json\\\/wp\\\/v2\\\/comments\"}],\"author\":[{\"embeddable\":true,\"href\":\"https:\\\/\\\/newapi.getpop.org\\\/wp-json\\\/wp\\\/v2\\\/users\\\/3\"}],\"up\":[{\"embeddable\":true,\"post_type\":\"post\",\"href\":\"https:\\\/\\\/newapi.getpop.org\\\/wp-json\\\/wp\\\/v2\\\/posts\\\/28\"}]}}",
      "contentLengthHeader": "508",
      "cacheControlHeader": "max-age=300,no-store"
    }
  }
}

_sendGraphQLHTTPRequest

Beim Ausführen der folgenden Query:

{
  graphQLRequest: _sendGraphQLHTTPRequest(
    input: {
      endpoint: "https://newapi.getpop.org/api/graphql/"
      query: """
        query GetPosts($postIDs: [ID]!) {
          posts(filter: { ids: $postIDs }) {
            id
            title
          }
        }
      """
      variables: [
        {
          name: "postIDs",
          value: [1, 1499]
        }
      ]
    }
  )
}

...erhältst du die folgende Antwort:

{
  "data": {
    "graphQLRequest": {
      "data": {
        "posts": [
          {
            "id": 1499,
            "title": "COPE with WordPress: Post demo containing plenty of blocks"
          },
          {
            "id": 1,
            "title": "Hello world!"
          }
        ]
      }
    }
  }
}

Felder für mehrere Anfragen: _sendJSONObjectItemHTTPRequests, _sendJSONObjectCollectionHTTPRequests, _sendGraphQLHTTPRequests und _sendHTTPRequests

Diese Felder funktionieren ähnlich wie ihre entsprechenden Nicht-Mehrfach-Felder, rufen aber Daten von mehreren Endpunkten gleichzeitig ab, entweder asynchron (parallel) oder synchron (nacheinander). Die Antworten werden in einer Liste abgelegt, in derselben Reihenfolge, in der die URLs im Parameter urls definiert wurden.

Zum Beispiel liefert die folgende Query:

{
  weatherForecasts: _sendJSONObjectItemHTTPRequests(
    urls: [
      "https://api.weather.gov/gridpoints/TOP/31,80/forecast",
      "https://api.weather.gov/gridpoints/TOP/41,55/forecast"
    ]
  )
}

...diese Antwort:

{
  "data": {
    "weatherForecasts": [
      {
        "type": "Feature",
        "geometry": {
          "type": "Polygon",
          "coordinates": [
            [
              [
                -97.1089731,
                39.766826299999998
              ],
              [
                -97.108526900000001,
                39.744778799999999
              ]
            ]
          ]
        },
        "properties": {
          "updated": "2022-03-04T09:39:46+00:00",
          "units": "us",
          "forecastGenerator": "BaselineForecastGenerator",
          "generatedAt": "2022-03-04T10:31:47+00:00",
          "updateTime": "2022-03-04T09:39:46+00:00",
          "validTimes": "2022-03-04T03:00:00+00:00/P7DT22H",
          "elevation": {
            "unitCode": "wmoUnit:m",
            "value": 441.95999999999998
          }
        }
      },
      {
        "type": "Feature",
        "geometry": {
          "type": "Polygon",
          "coordinates": [
            [
              [
                -96.812529900000001,
                39.218048000000003
              ],
              [
                -96.812148500000006,
                39.195940300000004
              ]
            ]
          ]
        },
        "properties": {
          "updated": "2022-03-04T09:39:46+00:00",
          "units": "us",
          "forecastGenerator": "BaselineForecastGenerator",
          "generatedAt": "2022-03-04T10:42:26+00:00",
          "updateTime": "2022-03-04T09:39:46+00:00",
          "validTimes": "2022-03-04T03:00:00+00:00/P7DT22H",
          "elevation": {
            "unitCode": "wmoUnit:m",
            "value": 409.04160000000002
          }
        }
      }
    ]
  }
}

Synchrone vs. asynchrone Ausführung

Diese Felder ermöglichen es dir, mehrere Anfragen auszuführen:

  • _sendHTTPRequests
  • _sendJSONObjectItemHTTPRequests
  • _sendJSONObjectCollectionHTTPRequests
  • _sendGraphQLHTTPRequests

Diese Felder empfangen den Input $async, um festzulegen, ob die Anfragen synchron ($async => false) oder asynchron ausgeführt werden sollen.

Synchrone Ausführung

Die HTTP-Anfragen werden der Reihe nach ausgeführt, jeweils unmittelbar nachdem die vorherige aufgelöst wurde.

Wenn alle HTTP-Anfragen erfolgreich sind, gibt das Feld ein Array mit ihren Antworten aus, in derselben Reihenfolge, in der sie in der Eingabeliste erscheinen.

Wenn eine HTTP-Anfrage fehlschlägt, stoppt die Ausführung sofort, d. h. die nachfolgenden HTTP-Anfragen in der Eingabeliste werden nicht ausgeführt.

Mögliche Ursachen für fehlgeschlagene HTTP-Anfragen sind:

  • Der Server, zu dem eine Verbindung hergestellt werden soll, ist offline
  • Der Statuscode der Antwort ist nicht 200: ein interner 500-Fehler, ein 404 Not Found, ein 403 Forbidden usw.
  • Der Content Type der Antwort ist nicht application/json

(Die letzteren beiden werden von _sendJSONObjectItemHTTPRequests, _sendJSONObjectCollectionHTTPRequests und _sendGraphQLHTTPRequests als Fehler behandelt, da diese nur JSON-Typen verarbeiten, nicht jedoch von _sendHTTPRequests, das keine Vorgaben macht.)

Im Fehlerfall gibt das Feld null zurück (d. h. die Antwort einer vorherigen erfolgreichen HTTP-Anfrage wird nicht ausgegeben), und der Fehlereintrag enthält die Erweiterung httpRequestInputArrayPosition, um anzuzeigen, welches Element der Eingabeliste fehlgeschlagen ist (beginnend bei 0):

{
  "errors": [
    {
      "message": "Server error: `GET https:\/\/mysite.com\/page-triggering-some-500-error` resulted in a `500 Internal Server Error` response",
      "extensions": {
        "httpRequestInputArrayPosition": 0,
        "field": "_sendJSONObjectItemHTTPRequests(async: false, inputs: [{url: \"https:\/\/mysite.com\/page-triggering-some-500-error\"}, {url: \"https:\/\/mysite.com\/wp-json\/wp\/v2\/posts\/1\/\"}, {url: \"https:\/\/mysite.com\/wp-json\/wp\/v2\/users\/1\/\"}])"
      }
    }
  ],
  "data": {
    "_sendJSONObjectItemHTTPRequests": null
  }
}

Asynchrone Ausführung

Alle HTTP-Anfragen werden gleichzeitig ausgeführt (d. h. parallel), und es ist nicht bekannt, in welcher Reihenfolge die HTTP-Anfragen aufgelöst werden.

Wenn alle HTTP-Anfragen erfolgreich sind, gibt das Feld ein Array mit ihren Antworten aus, in derselben Reihenfolge, in der sie in der Eingabeliste erscheinen.

Sobald eine HTTP-Anfrage fehlschlägt, stoppt die Ausführung sofort, jedoch können zu diesem Zeitpunkt alle anderen HTTP-Anfragen bereits ebenfalls ausgeführt worden sein.

Außerdem gibt der Server nicht an, welches Element der Liste fehlgeschlagen ist (beachte, dass in der folgenden Antwort keine httpRequestInputArrayPosition-Erweiterung vorhanden ist):

{
  "errors": [
    {
      "message": "Server error: `GET https:\/\/mysite.com\/page-triggering-some-500-error` resulted in a `500 Internal Server Error` response",
      "extensions": {
        "field": "_sendJSONObjectItemHTTPRequests(async: true, inputs: [{url: \"https:\/\/mysite.com\/page-triggering-some-500-error\"}, {url: \"https:\/\/mysite.com\/wp-json\/wp\/v2\/posts\/1\/\"}, {url: \"https:\/\/mysite.com\/wp-json\/wp\/v2\/users\/1\/\"}])"
      }
    }
  ],
  "data": {
    "_sendJSONObjectItemHTTPRequests": null
  }
}

Globale Felder

Alle diese Felder sind Global Fields und werden daher jedem einzelnen Typ im GraphQL-Schema hinzugefügt: in QueryRoot, aber auch in Post, User, Comment usw.

Dies ermöglicht es uns, in derselben GraphQL-Query eine Verbindung zu einem externen API-Endpunkt herzustellen, der zur Laufzeit basierend auf den in einer Entität gespeicherten Daten generiert wird.

Zum Beispiel können wir eine Liste von Benutzern in unserer Datenbank durchlaufen und für jeden eine Verbindung zu einem externen System (z. B. einem CRM) herstellen, um weitere Daten über sie abzurufen.

In dieser Query generieren wir den API-Endpunkt mithilfe der Field to Input-Funktion und des Funktionsfeldes _arrayJoin:

{
  users(
    pagination: { limit: 2 },
    sort: { order: ASC, by: ID }
  ) {
    id
    endpoint: _arrayJoin(values: [
      "https://newapi.getpop.org/wp-json/wp/v2/users/",
      $__id,
      "?_fields=name"
    ])
    _sendJSONObjectItemHTTPRequest(input: { url: $__endpoint } )
  }
}

...ergibt:

{
  "data": {
    "users": [
      {
        "id": 1,
        "endpoint": "https://newapi.getpop.org/wp-json/wp/v2/users/1?_fields=name",
        "_sendJSONObjectItemHTTPRequest": {
          "name": "leo",
          "_links": {
            "self": [
              {
                "href": "https://newapi.getpop.org/wp-json/wp/v2/users/1"
              }
            ],
            "collection": [
              {
                "href": "https://newapi.getpop.org/wp-json/wp/v2/users"
              }
            ]
          }
        }
      },
      {
        "id": 2,
        "endpoint": "https://newapi.getpop.org/wp-json/wp/v2/users/2?_fields=name",
        "_sendJSONObjectItemHTTPRequest": {
          "name": "themedemos",
          "_links": {
            "self": [
              {
                "href": "https://newapi.getpop.org/wp-json/wp/v2/users/2"
              }
            ],
            "collection": [
              {
                "href": "https://newapi.getpop.org/wp-json/wp/v2/users"
              }
            ]
          }
        }
      }
    ]
  }
}