HTTP-клиент
Добавление полей в схему GraphQL для выполнения HTTP-запросов к веб-серверу и получения ответов:
_sendJSONObjectItemHTTPRequest_sendJSONObjectItemHTTPRequests_sendJSONObjectCollectionHTTPRequest_sendJSONObjectCollectionHTTPRequests_sendHTTPRequest_sendHTTPRequests_sendGraphQLHTTPRequest_sendGraphQLHTTPRequests
По соображениям безопасности URL-адреса, к которым можно подключаться, должны быть явно указаны в настройках.
Список полей
Следующие поля добавляются в схему.
_sendJSONObjectItemHTTPRequest
Возвращает ответ (REST) для одного объекта JSON.
Сигнатура: _sendJSONObjectItemHTTPRequest(input: HTTPRequestInput!): JSONObject.
_sendJSONObjectItemHTTPRequests
Возвращает ответ (REST) для одного объекта JSON с нескольких endpoint-ов, выполняемых асинхронно (параллельно) или синхронно (один за другим).
Сигнатура: _sendJSONObjectItemHTTPRequests(async: Boolean = true, inputs: [HTTPRequestInput!]!): [JSONObject].
_sendJSONObjectCollectionHTTPRequest
Возвращает ответ (REST) для коллекции объектов JSON.
Сигнатура: _sendJSONObjectCollectionHTTPRequest(input: HTTPRequestInput!): [JSONObject].
_sendJSONObjectCollectionHTTPRequests
Возвращает ответ (REST) для коллекции объектов JSON с нескольких endpoint-ов, выполняемых асинхронно (параллельно) или синхронно (один за другим).
Сигнатура: _sendJSONObjectCollectionHTTPRequests(async: Boolean = true, inputs: [HTTPRequestInput!]!): [[JSONObject]].
_sendHTTPRequest
Подключается к указанному URL и возвращает объект HTTPResponse, содержащий следующие поля:
statusCode: Int!contentType: String!body: String!headers: JSONObject!header(name: String!): StringhasHeader(name: String!): Boolean!
Сигнатура: _sendHTTPRequest(input: HTTPRequestInput!): HTTPResponse.
_sendHTTPRequests
Аналогично _sendHTTPRequest, но принимает несколько URL и позволяет подключаться к ним асинхронно (параллельно).
Сигнатура: _sendHTTPRequests(async: Boolean = true, inputs: [HTTPRequestInput!]!): [HTTPResponse].
_sendGraphQLHTTPRequest
Выполняет GraphQL query к указанному endpoint и возвращает ответ в виде объекта JSON.
Входные данные этого поля принимают данные, ожидаемые для GraphQL: endpoint, GraphQL query, переменные и имя операции, а метод по умолчанию (POST) и content type (application/json) уже заданы.
Сигнатура: _sendGraphQLHTTPRequest(input: GraphQLRequestInput!): JSONObject.
_sendGraphQLHTTPRequests
Аналогично _sendGraphQLHTTPRequests, но выполняет несколько GraphQL queries одновременно — асинхронно (параллельно) или синхронно (один за другим).
Сигнатура: _sendGraphQLHTTPRequests(async: Boolean = true, inputs: [GraphQLRequestInput!]!): JSONObject.
Настройка разрешённых URL-адресов
Необходимо настроить список URL-адресов, к которым разрешено подключаться.
Каждая запись может быть:
- Регулярным выражением (regex), если оно заключено в
/или#, либо - Полным URL-адресом в остальных случаях
Например, любая из следующих записей совпадает с URL "https://gatographql.com/recipes/":
https://gatographql.com/recipes/#https://gatographql.com/recipes/?##https://gatographql.com/.*#/https:\\/\\/gatographql.com\\/(\S+)/
Эту настройку можно выполнить в 2 местах (в порядке приоритета):
- Пользовательская: в соответствующей конфигурации схемы
- Общая: на странице настроек
В конфигурации схемы, применённой к endpoint, выберите опцию "Use custom configuration" и введите нужные записи:

В противном случае будут использованы записи, заданные на вкладке «Send HTTP Request Fields» в настройках:

Существует 2 режима поведения — «Разрешить доступ» и «Запретить доступ»:
- Разрешить доступ: доступны только настроенные записи, все остальные — нет
- Запретить доступ: настроенные записи недоступны, все остальные — доступны

Необходимые права для доступа к внутренним URL-адресам
Некоторые URL-адреса разрешаются во внутренние адреса (127.0.0.1, диапазоны link-local, endpoint-ы cloud-metadata и т.д.), что может открыть доступ к внутренним сервисам. Этот параметр настраивается на странице настроек в разделе Plugin Configuration > HTTP Client.

Возможность WordPress, которой должен обладать запрашивающий пользователь для обращения к URL-адресам, разрешающимся во внутренние адреса (127.0.0.1, диапазоны link-local, endpoint-ы cloud-metadata и т.д.).
По умолчанию задано manage_options, чтобы не-администраторы не могли получить доступ к внутренним сервисам через поля HTTP-клиента.
Выберите (любой авторизованный пользователь), чтобы отключить проверку прав.
Когда использовать каждое поле
Все поля похожи, но различаются.
_sendJSONObjectItemHTTPRequest
Это поле возвращает один элемент объекта JSON, что полезно при запросе одного элемента из REST endpoint, например из endpoint WP REST API /wp-json/wp/v2/posts/1/.
Этот query:
{
postData: _sendJSONObjectItemHTTPRequest(input: { url: "https://newapi.getpop.org/wp-json/wp/v2/posts/1/" } )
}...возвращает следующий ответ:
{
"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’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’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
Это поле аналогично _sendJSONObjectItemHTTPRequest, но возвращает коллекцию объектов JSON, как из endpoint WP REST API /wp-json/wp/v2/posts/.
Этот query:
{
postData: _sendJSONObjectItemHTTPRequest(input: { url: "https://newapi.getpop.org/wp-json/wp/v2/posts/?per_page=3&_fields=id,type,title,date" } )
}...возвращает следующий ответ:
{
"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 – teaser"
}
},
{
"id": 1499,
"date": "2019-08-08T02:49:36",
"type": "post",
"title": {
"rendered": "COPE with WordPress: Post demo containing plenty of blocks"
}
}
]
}
}_sendHTTPRequest
Это поле возвращает объект HTTPResponse со всеми свойствами ответа, что позволяет независимо запрашивать тело ответа (которое имеет тип String, то есть не приводится к JSON), код состояния, content type и заголовки.
Например, следующий 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")
}
}...возвращает следующий ответ:
{
"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
Выполнение следующего 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]
}
]
}
)
}...возвращает следующий ответ:
{
"data": {
"graphQLRequest": {
"data": {
"posts": [
{
"id": 1499,
"title": "COPE with WordPress: Post demo containing plenty of blocks"
},
{
"id": 1,
"title": "Hello world!"
}
]
}
}
}
}Поля для множественных запросов: _sendJSONObjectItemHTTPRequests, _sendJSONObjectCollectionHTTPRequests, _sendGraphQLHTTPRequests и _sendHTTPRequests
Эти поля работают аналогично соответствующим одиночным полям, но получают данные сразу с нескольких endpoint-ов — асинхронно (параллельно) или синхронно (один за другим). Ответы помещаются в список в том же порядке, в котором URL были заданы в параметре urls.
Например, следующий query:
{
weatherForecasts: _sendJSONObjectItemHTTPRequests(
urls: [
"https://api.weather.gov/gridpoints/TOP/31,80/forecast",
"https://api.weather.gov/gridpoints/TOP/41,55/forecast"
]
)
}...возвращает следующий ответ:
{
"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
}
}
}
]
}
}Синхронное и асинхронное выполнение
Следующие поля позволяют выполнять множественные запросы:
_sendHTTPRequests_sendJSONObjectItemHTTPRequests_sendJSONObjectCollectionHTTPRequests_sendGraphQLHTTPRequests
Эти поля принимают входной параметр $async, определяющий, должны ли запросы выполняться синхронно ($async => false) или асинхронно.
Синхронное выполнение
HTTP-запросы выполняются последовательно: каждый следующий запрос запускается сразу после завершения предыдущего.
Если все HTTP-запросы завершились успешно, поле вернёт массив с ответами в том же порядке, в котором они указаны во входном списке.
Если какой-либо HTTP-запрос завершится с ошибкой, выполнение остановится в этом месте, то есть последующие HTTP-запросы из входного списка не будут выполнены.
Возможные причины ошибок HTTP-запросов:
- Сервер, к которому производится подключение, недоступен (offline)
- Код состояния ответа не равен 200: внутренняя ошибка 500, не найдено 404, доступ запрещён 403 и т.д.
- Content type ответа не является
application/json
(Последние два варианта расцениваются как ошибка в _sendJSONObjectItemHTTPRequests, _sendJSONObjectCollectionHTTPRequests и _sendGraphQLHTTPRequests, которые ожидают работу только с типами JSON, но не в _sendHTTPRequests, который не накладывает подобных ограничений.)
В случае ошибки поле возвращает null (то есть ответы от предыдущих успешных HTTP-запросов не будут выведены), а запись об ошибке будет содержать расширение httpRequestInputArrayPosition, указывающее, какой элемент входного списка вызвал ошибку (нумерация с 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
}
}Асинхронное выполнение
Все HTTP-запросы выполняются одновременно (то есть параллельно), и порядок их завершения заранее не известен.
Если все HTTP-запросы завершились успешно, поле вернёт массив с ответами в том же порядке, в котором они указаны во входном списке.
При возникновении ошибки в любом из HTTP-запросов выполнение немедленно останавливается, однако к этому моменту все остальные HTTP-запросы могут уже быть выполнены.
Кроме того, сервер не укажет, какой именно элемент списка вызвал ошибку (обратите внимание на отсутствие расширения 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
}
}Глобальные поля
Все эти поля являются Global Fields и добавляются в каждый тип схемы GraphQL: в QueryRoot, а также в Post, User, Comment и т.д.
Это позволяет подключаться к внешнему API endpoint, сформированному во время выполнения, в рамках того же GraphQL query, используя данные, хранящиеся в некоторой сущности.
Например, можно перебрать список пользователей в нашей базе данных и для каждого из них обратиться к внешней системе (например, CRM) для получения дополнительных данных.
В этом query endpoint API генерируется с помощью функции Field to Input и функционального поля _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 } )
}
}...возвращает:
{
"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"
}
]
}
}
}
]
}
}