HTTP Client
Adição de campos ao schema GraphQL para executar requisições HTTP a um servidor web e obter suas respostas:
_sendJSONObjectItemHTTPRequest_sendJSONObjectItemHTTPRequests_sendJSONObjectCollectionHTTPRequest_sendJSONObjectCollectionHTTPRequests_sendHTTPRequest_sendHTTPRequests_sendGraphQLHTTPRequest_sendGraphQLHTTPRequests
Por razões de segurança, as URLs às quais é possível se conectar devem ser configuradas explicitamente.
Lista de campos
Os campos a seguir são adicionados ao schema.
_sendJSONObjectItemHTTPRequest
Recupera a resposta (REST) para um único objeto JSON.
Signature: _sendJSONObjectItemHTTPRequest(input: HTTPRequestInput!): JSONObject.
_sendJSONObjectItemHTTPRequests
Recupera a resposta (REST) para um único objeto JSON a partir de múltiplos endpoints, executados de forma assíncrona (em paralelo) ou síncrona (um após o outro).
Signature: _sendJSONObjectItemHTTPRequests(async: Boolean = true, inputs: [HTTPRequestInput!]!): [JSONObject].
_sendJSONObjectCollectionHTTPRequest
Recupera a resposta (REST) para uma coleção de objetos JSON.
Signature: _sendJSONObjectCollectionHTTPRequest(input: HTTPRequestInput!): [JSONObject].
_sendJSONObjectCollectionHTTPRequests
Recupera a resposta (REST) para uma coleção de objetos JSON a partir de múltiplos endpoints, executados de forma assíncrona (em paralelo) ou síncrona (um após o outro).
Signature: _sendJSONObjectCollectionHTTPRequests(async: Boolean = true, inputs: [HTTPRequestInput!]!): [[JSONObject]].
_sendHTTPRequest
Conecta-se à URL especificada e recupera um objeto HTTPResponse, que contém os seguintes campos:
statusCode: Int!contentType: String!body: String!headers: JSONObject!header(name: String!): StringhasHeader(name: String!): Boolean!
Signature: _sendHTTPRequest(input: HTTPRequestInput!): HTTPResponse.
_sendHTTPRequests
Semelhante a _sendHTTPRequest, mas recebe múltiplas URLs e permite conectar-se a elas de forma assíncrona (em paralelo).
Signature: _sendHTTPRequests(async: Boolean = true, inputs: [HTTPRequestInput!]!): [HTTPResponse].
_sendGraphQLHTTPRequest
Executa uma query GraphQL no endpoint fornecido e recupera a resposta como um objeto JSON.
O input deste campo aceita os dados esperados para GraphQL: o endpoint, a query GraphQL, as variáveis e o nome da operação, e já define o método padrão (POST) e o content type (application/json).
Signature: _sendGraphQLHTTPRequest(input: GraphQLRequestInput!): JSONObject.
_sendGraphQLHTTPRequests
Semelhante a _sendGraphQLHTTPRequests, mas executa múltiplas queries GraphQL de forma concorrente, seja assincronamente (em paralelo) ou sincronamente (uma após a outra).
Signature: _sendGraphQLHTTPRequests(async: Boolean = true, inputs: [GraphQLRequestInput!]!): JSONObject.
Configurando as URLs permitidas
Devemos configurar a lista de URLs às quais podemos nos conectar.
Cada entrada pode ser:
- Uma regex (expressão regular), se estiver delimitada por
/ou#, ou - A URL completa, caso contrário
Por exemplo, qualquer uma dessas entradas corresponde à URL "https://gatographql.com/recipes/":
https://gatographql.com/recipes/#https://gatographql.com/recipes/?##https://gatographql.com/.*#/https:\\/\\/gatographql.com\\/(\S+)/
Há 2 lugares onde essa configuração pode ser feita, em ordem de prioridade:
- Personalizada: Na Configuração de Schema correspondente
- Geral: Na página de Configurações
Na Configuração de Schema aplicada ao endpoint, selecione a opção "Use custom configuration" e insira as entradas desejadas:

Caso contrário, serão usadas as entradas definidas na aba "Send HTTP Request Fields" das Configurações:

Há 2 comportamentos, "Allow access" e "Deny access":
- Allow access: apenas as entradas configuradas podem ser acessadas, nenhuma outra pode
- Deny access: as entradas configuradas não podem ser acessadas, todas as outras podem

Capability necessária para acessar URLs internas
Algumas URLs resolvem para endereços internos (127.0.0.1, intervalos link-local, endpoints de cloud-metadata, etc.) que podem expor serviços internos se acessados. Esta configuração é feita na página de Configurações, em Plugin Configuration > HTTP Client.

Capability do WordPress que o usuário solicitante deve ter para acessar URLs que resolvem para endereços internos (127.0.0.1, intervalos link-local, endpoints de cloud-metadata, etc.).
O padrão é manage_options para que usuários não administradores não consigam acessar serviços internos por meio dos campos do HTTP Client.
Selecione (qualquer usuário autenticado) para desabilitar a verificação de capability.
Quando usar cada campo
Todos os campos são semelhantes, mas diferentes.
_sendJSONObjectItemHTTPRequest
Este campo recupera um item de objeto JSON, o que é útil ao consultar um único item de um endpoint REST, como o endpoint da WP REST API /wp-json/wp/v2/posts/1/.
Esta query:
{
postData: _sendJSONObjectItemHTTPRequest(input: { url: "https://newapi.getpop.org/wp-json/wp/v2/posts/1/" } )
}...recupera esta resposta:
{
"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
Este campo é semelhante a _sendJSONObjectItemHTTPRequest, mas recupera uma coleção de objetos JSON, como do endpoint da WP REST API /wp-json/wp/v2/posts/.
Esta query:
{
postData: _sendJSONObjectItemHTTPRequest(input: { url: "https://newapi.getpop.org/wp-json/wp/v2/posts/?per_page=3&_fields=id,type,title,date" } )
}...recupera esta resposta:
{
"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
Este campo recupera um objeto HTTPResponse com todas as propriedades da resposta, para que possamos consultar independentemente o body (que é do tipo String, ou seja, não é convertido para JSON), o código de status, o content type e os headers.
Por exemplo, a seguinte 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")
}
}...retorna esta resposta:
{
"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
Executando a seguinte 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]
}
]
}
)
}...retorna a seguinte resposta:
{
"data": {
"graphQLRequest": {
"data": {
"posts": [
{
"id": 1499,
"title": "COPE with WordPress: Post demo containing plenty of blocks"
},
{
"id": 1,
"title": "Hello world!"
}
]
}
}
}
}Campos de múltiplas requisições: _sendJSONObjectItemHTTPRequests, _sendJSONObjectCollectionHTTPRequests, _sendGraphQLHTTPRequests e _sendHTTPRequests
Esses campos funcionam de forma semelhante aos seus campos não-múltiplos correspondentes, mas recuperam dados de vários endpoints ao mesmo tempo, seja de forma assíncrona (em paralelo) ou síncrona (um após o outro). As respostas são colocadas em uma lista, na mesma ordem em que as URLs foram definidas no parâmetro urls.
Por exemplo, a seguinte query:
{
weatherForecasts: _sendJSONObjectItemHTTPRequests(
urls: [
"https://api.weather.gov/gridpoints/TOP/31,80/forecast",
"https://api.weather.gov/gridpoints/TOP/41,55/forecast"
]
)
}...produz esta resposta:
{
"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
}
}
}
]
}
}Execução síncrona vs assíncrona
Esses campos nos permitem executar múltiplas requisições:
_sendHTTPRequests_sendJSONObjectItemHTTPRequests_sendJSONObjectCollectionHTTPRequests_sendGraphQLHTTPRequests
Esses campos recebem o input $async, para definir se as requisições devem ser executadas de forma síncrona ($async => false) ou assíncrona.
Execução síncrona
As requisições HTTP são executadas em ordem, com cada uma sendo executada imediatamente após a anterior ter sido resolvida.
Quando todas as requisições HTTP são bem-sucedidas, o campo imprimirá um array com suas respostas, na mesma ordem em que aparecem na lista de input.
Se alguma requisição HTTP falhar, a execução para imediatamente, ou seja, as requisições HTTP subsequentes na lista de input não são executadas.
Algumas possíveis causas de falha nas requisições HTTP são:
- O servidor ao qual se conectar está offline
- O código de status da resposta não é 200: um erro interno 500, um 404 não encontrado, um 403 proibido, etc.
- O content type da resposta não é
application/json
(Os dois últimos são tratados como erro por _sendJSONObjectItemHTTPRequests, _sendJSONObjectCollectionHTTPRequests e _sendGraphQLHTTPRequests, que esperam lidar apenas com tipos JSON, mas não por _sendHTTPRequests, que não impõe restrições.)
Em caso de erro, o campo retorna null (ou seja, a resposta de qualquer requisição HTTP anterior bem-sucedida não será impressa), e a entrada de erro conterá a extensão httpRequestInputArrayPosition para indicar qual é o item da lista de input que falhou (começando em 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
}
}Execução assíncrona
Todas as requisições HTTP são executadas de forma concorrente (ou seja, em paralelo), e não se sabe em que ordem as requisições HTTP serão resolvidas.
Quando todas as requisições HTTP são bem-sucedidas, o campo imprimirá um array com suas respostas, na mesma ordem em que aparecem na lista de input.
Sempre que alguma requisição HTTP falhar, a execução para imediatamente; porém, nesse momento, todas as outras requisições HTTP podem já ter sido executadas também.
Além disso, o servidor não indicará qual é o item da lista que falhou (observe que não há a extensão httpRequestInputArrayPosition na resposta abaixo):
{
"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
}
}Campos Globais
Todos esses campos são Global Fields, portanto são adicionados a cada tipo no schema GraphQL: em QueryRoot, mas também em Post, User, Comment, etc.
Isso nos permite conectar a algum endpoint de API externa gerado em tempo de execução na mesma query GraphQL, com base nos dados armazenados em alguma entidade.
Por exemplo, podemos iterar uma lista de usuários em nosso banco de dados e, para cada um, conectar a um sistema externo (como um CRM) para recuperar mais dados sobre eles.
Nesta query, geramos o endpoint da API usando o recurso Field to Input e o campo function _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 } )
}
}...produzindo:
{
"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"
}
]
}
}
}
]
}
}