HTTP Client
HTTP ClientHTTP Client

HTTP Client

Included in the “Power Extensions” bundle

Dodanie pól do schematu GraphQL w celu wykonywania zapytań HTTP do serwera WWW i pobierania ich odpowiedzi:

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

Ze względów bezpieczeństwa adresy URL, z którymi można się łączyć, muszą być jawnie skonfigurowane.

Lista pól

Poniższe pola są dodawane do schematu.

_sendJSONObjectItemHTTPRequest

Pobiera odpowiedź (REST) dla pojedynczego obiektu JSON.

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

_sendJSONObjectItemHTTPRequests

Pobiera odpowiedź (REST) dla pojedynczego obiektu JSON z wielu endpointów, wykonywanych asynchronicznie (równolegle) lub synchronicznie (jeden po drugim).

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

_sendJSONObjectCollectionHTTPRequest

Pobiera odpowiedź (REST) dla kolekcji obiektów JSON.

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

_sendJSONObjectCollectionHTTPRequests

Pobiera odpowiedź (REST) dla kolekcji obiektów JSON z wielu endpointów, wykonywanych asynchronicznie (równolegle) lub synchronicznie (jeden po drugim).

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

_sendHTTPRequest

Łączy się z podanym adresem URL i pobiera obiekt HTTPResponse, który zawiera następujące pola:

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

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

_sendHTTPRequests

Podobne do _sendHTTPRequest, ale przyjmuje wiele adresów URL i umożliwia łączenie się z nimi asynchronicznie (równolegle).

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

_sendGraphQLHTTPRequest

Wykonuje query GraphQL na podanym endpoincie i pobiera odpowiedź jako obiekt JSON.

Dane wejściowe tego pola przyjmują dane oczekiwane dla GraphQL: endpoint, query GraphQL, zmienne i nazwę operacji, oraz automatycznie ustawia domyślną metodę (POST) i typ zawartości (application/json).

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

_sendGraphQLHTTPRequests

Podobne do _sendGraphQLHTTPRequests, ale wykonuje wiele queries GraphQL jednocześnie, zarówno asynchronicznie (równolegle), jak i synchronicznie (jedna po drugiej).

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

Konfigurowanie dozwolonych adresów URL

Musimy skonfigurować listę adresów URL, z którymi możemy się łączyć.

Każdy wpis może być:

  • Wyrażeniem regularnym (regex), jeśli jest otoczony znakami / lub #, lub
  • Kompletnym adresem URL w przeciwnym razie

Na przykład każdy z tych wpisów pasuje do adresu URL "https://gatographql.com/recipes/":

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

Istnieją 2 miejsca, w których można przeprowadzić tę konfigurację, według priorytetu:

  1. Niestandardowa: W odpowiedniej Konfiguracji Schematu
  2. Ogólna: Na stronie Ustawień

W Konfiguracji Schematu zastosowanej do endpointu wybierz opcję "Use custom configuration" i wprowadź żądane wpisy:

Definiowanie wpisów dla Konfiguracji Schematu

W przeciwnym razie zostaną użyte wpisy zdefiniowane w zakładce "Send HTTP Request Fields" w Ustawieniach:

Definiowanie wpisów w Ustawieniach
Definiowanie wpisów w Ustawieniach

Istnieją 2 zachowania: "Allow access" i "Deny access":

  • Allow access: tylko skonfigurowane wpisy mogą być dostępne, żadne inne nie mogą
  • Deny access: skonfigurowane wpisy nie mogą być dostępne, wszystkie pozostałe mogą
Definiowanie zachowania dostępu
Definiowanie zachowania dostępu

Wymagana zdolność dostępu do wewnętrznych adresów URL

Niektóre adresy URL rozwiązują się do adresów wewnętrznych (127.0.0.1, zakresy link-local, endpointy cloud-metadata itp.), które mogą ujawnić wewnętrzne usługi, jeśli zostaną osiągnięte. To ustawienie jest konfigurowane na stronie Ustawień, w sekcji Plugin Configuration > HTTP Client.

Ustawianie wymaganej zdolności dostępu do wewnętrznych adresów URL
Ustawianie wymaganej zdolności dostępu do wewnętrznych adresów URL

Zdolność WordPress, którą musi posiadać żądający użytkownik, aby móc kierować żądania do adresów URL, które rozwiązują się do adresów wewnętrznych (127.0.0.1, zakresy link-local, endpointy cloud-metadata itp.).

Domyślnie ustawione na manage_options, aby użytkownicy niebędący administratorami nie mogli uzyskiwać dostępu do wewnętrznych usług za pośrednictwem pól HTTP Client.

Wybierz (dowolny zalogowany użytkownik), aby wyłączyć sprawdzanie zdolności.

Kiedy używać każdego pola

Wszystkie pola są podobne, ale różne.

_sendJSONObjectItemHTTPRequest

To pole pobiera pojedynczy element obiektu JSON, co jest przydatne przy zapytaniu o jeden element z endpointu REST, np. z endpointu WP REST API /wp-json/wp/v2/posts/1/.

Ta query:

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

...pobiera tę odpowiedź:

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

To pole jest podobne do _sendJSONObjectItemHTTPRequest, ale pobiera kolekcję obiektów JSON, np. z endpointu WP REST API /wp-json/wp/v2/posts/.

Ta query:

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

...pobiera tę odpowiedź:

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

To pole pobiera obiekt HTTPResponse ze wszystkimi właściwościami odpowiedzi, dzięki czemu możemy niezależnie zapytać o body (które jest typu String, czyli nie jest rzutowane na JSON), kod statusu, typ zawartości i nagłówki.

Na przykład poniższa 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")
  }
}

...zwraca tę odpowiedź:

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

Wykonując poniższą 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]
        }
      ]
    }
  )
}

...zwraca następującą odpowiedź:

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

Pola wielokrotnych zapytań: _sendJSONObjectItemHTTPRequests, _sendJSONObjectCollectionHTTPRequests, _sendGraphQLHTTPRequests i _sendHTTPRequests

Te pola działają podobnie do swoich odpowiedników nieobsługujących wielu zapytań, ale pobierają dane z kilku endpointów jednocześnie, zarówno asynchronicznie (równolegle), jak i synchronicznie (jeden po drugim). Odpowiedzi są umieszczane na liście w tej samej kolejności, w jakiej adresy URL zostały zdefiniowane w parametrze urls.

Na przykład poniższa query:

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

...produkuje tę odpowiedź:

{
  "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
          }
        }
      }
    ]
  }
}

Wykonanie synchroniczne a asynchroniczne

Te pola umożliwiają wykonanie wielu zapytań:

  • _sendHTTPRequests
  • _sendJSONObjectItemHTTPRequests
  • _sendJSONObjectCollectionHTTPRequests
  • _sendGraphQLHTTPRequests

Te pola przyjmują dane wejściowe $async, aby określić, czy zapytania mają być wykonywane synchronicznie ($async => false) czy asynchronicznie.

Wykonanie synchroniczne

Zapytania HTTP są wykonywane w kolejności, przy czym każde jest wykonywane bezpośrednio po rozwiązaniu poprzedniego.

Gdy wszystkie zapytania HTTP zakończą się powodzeniem, pole wydrukuje tablicę z ich odpowiedziami w tej samej kolejności, w jakiej pojawiają się na liście wejściowej.

Jeśli którekolwiek zapytanie HTTP zakończy się niepowodzeniem, wykonanie zatrzymuje się w tym miejscu, tzn. kolejne zapytania HTTP na liście wejściowej nie są wykonywane.

Niektóre możliwe przyczyny niepowodzenia zapytań HTTP to:

  • Serwer, z którym chcemy się połączyć, jest offline
  • Kod statusu odpowiedzi nie wynosi 200: błąd wewnętrzny 500, brak strony 404, brak dostępu 403 itp.
  • Typ zawartości odpowiedzi nie jest application/json

(Dwa ostatnie są traktowane jako błąd przez _sendJSONObjectItemHTTPRequests, _sendJSONObjectCollectionHTTPRequests i _sendGraphQLHTTPRequests, które oczekują obsługi wyłącznie typów JSON, ale nie przez _sendHTTPRequests, które nie narzuca ograniczeń.)

W przypadku błędu pole zwraca null (tzn. odpowiedź poprzednich udanych zapytań HTTP nie będzie wydrukowana), a wpis błędu będzie zawierał rozszerzenie httpRequestInputArrayPosition wskazujące, który element listy wejściowej zakończył się niepowodzeniem (zaczynając od 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
  }
}

Wykonanie asynchroniczne

Wszystkie zapytania HTTP są wykonywane współbieżnie (tzn. równolegle) i nie wiadomo, w jakiej kolejności zostaną rozwiązane.

Gdy wszystkie zapytania HTTP zakończą się powodzeniem, pole wydrukuje tablicę z ich odpowiedziami w tej samej kolejności, w jakiej pojawiają się na liście wejściowej.

Gdy którekolwiek zapytanie HTTP zakończy się niepowodzeniem, wykonanie zatrzymuje się natychmiast; jednak w tym momencie wszystkie pozostałe zapytania HTTP mogły już zostać wykonane.

Ponadto serwer nie wskaże, który element listy zakończył się niepowodzeniem (zwróć uwagę, że w poniższej odpowiedzi nie ma rozszerzenia httpRequestInputArrayPosition):

{
  "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
  }
}

Pola globalne

Wszystkie te pola są Global Fields, więc są dodawane do każdego typu w schemacie GraphQL: w QueryRoot, ale także w Post, User, Comment itp.

Pozwala nam to łączyć się z zewnętrznym endpointem API generowanym w czasie wykonywania w tej samej query GraphQL, na podstawie danych przechowywanych w danej encji.

Na przykład możemy iterować listę użytkowników w naszej bazie danych i dla każdego z nich łączyć się z zewnętrznym systemem (takim jak CRM), aby pobrać o nich dodatkowe dane.

W tej query generujemy endpoint API za pomocą funkcji Field to Input i pola funkcji _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 } )
  }
}

...produkując:

{
  "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"
              }
            ]
          }
        }
      }
    ]
  }
}