Cliente HTTP
Adición de campos al esquema GraphQL para ejecutar peticiones HTTP contra un servidor web y obtener su respuesta:
_sendJSONObjectItemHTTPRequest_sendJSONObjectItemHTTPRequests_sendJSONObjectCollectionHTTPRequest_sendJSONObjectCollectionHTTPRequests_sendHTTPRequest_sendHTTPRequests_sendGraphQLHTTPRequest_sendGraphQLHTTPRequests
Por motivos de seguridad, las URLs a las que se puede conectar deben configurarse explícitamente.
Lista de campos
Los siguientes campos se añaden al esquema.
_sendJSONObjectItemHTTPRequest
Obtiene la respuesta (REST) para un único objeto JSON.
Firma: _sendJSONObjectItemHTTPRequest(input: HTTPRequestInput!): JSONObject.
_sendJSONObjectItemHTTPRequests
Obtiene la respuesta (REST) para un único objeto JSON desde múltiples endpoints, ejecutados de forma asíncrona (en paralelo) o síncrona (uno tras otro).
Firma: _sendJSONObjectItemHTTPRequests(async: Boolean = true, inputs: [HTTPRequestInput!]!): [JSONObject].
_sendJSONObjectCollectionHTTPRequest
Obtiene la respuesta (REST) para una colección de objetos JSON.
Firma: _sendJSONObjectCollectionHTTPRequest(input: HTTPRequestInput!): [JSONObject].
_sendJSONObjectCollectionHTTPRequests
Obtiene la respuesta (REST) para una colección de objetos JSON desde múltiples endpoints, ejecutados de forma asíncrona (en paralelo) o síncrona (uno tras otro).
Firma: _sendJSONObjectCollectionHTTPRequests(async: Boolean = true, inputs: [HTTPRequestInput!]!): [[JSONObject]].
_sendHTTPRequest
Se conecta a la URL especificada y obtiene un objeto HTTPResponse, que contiene los siguientes campos:
statusCode: Int!contentType: String!body: String!headers: JSONObject!header(name: String!): StringhasHeader(name: String!): Boolean!
Firma: _sendHTTPRequest(input: HTTPRequestInput!): HTTPResponse.
_sendHTTPRequests
Similar a _sendHTTPRequest pero recibe múltiples URLs, y permite conectarse a ellas de forma asíncrona (en paralelo).
Firma: _sendHTTPRequests(async: Boolean = true, inputs: [HTTPRequestInput!]!): [HTTPResponse].
_sendGraphQLHTTPRequest
Ejecuta una consulta GraphQL contra el endpoint proporcionado, y obtiene la respuesta como objeto JSON.
La entrada de este campo acepta los datos esperados para GraphQL: el endpoint, la consulta GraphQL, las variables y el nombre de la operación, y ya establece el método por defecto (POST) y el content type (application/json).
Firma: _sendGraphQLHTTPRequest(input: GraphQLRequestInput!): JSONObject.
_sendGraphQLHTTPRequests
Similar a _sendGraphQLHTTPRequests pero ejecuta múltiples consultas GraphQL concurrentemente, ya sea de forma asíncrona (en paralelo) o síncrona (una tras otra).
Firma: _sendGraphQLHTTPRequests(async: Boolean = true, inputs: [GraphQLRequestInput!]!): JSONObject.
Configurando las URLs permitidas
Debemos configurar la lista de URLs a las que podemos conectarnos.
Cada entrada puede ser:
- Una regex (expresión regular), si está rodeada por
/o#, o - La URL completa, en caso contrario
Por ejemplo, cualquiera de estas entradas coincide con la URL "https://gatographql.com/recipes/":
https://gatographql.com/recipes/#https://gatographql.com/recipes/?##https://gatographql.com/.*#/https:\\/\\/gatographql.com\\/(\S+)/
Hay 2 lugares donde se puede realizar esta configuración, por orden de prioridad:
- Personalizada: En la Configuración del Esquema correspondiente
- General: En la página de Ajustes
En la Configuración del Esquema aplicada al endpoint, selecciona la opción "Usar configuración personalizada" y luego introduce las entradas deseadas:

De lo contrario, se utilizarán las entradas definidas en la pestaña "Send HTTP Request Fields" de los Ajustes:

Hay 2 comportamientos, "Permitir acceso" y "Denegar acceso":
- Permitir acceso: solo se pueden acceder las entradas configuradas, ninguna otra
- Denegar acceso: las entradas configuradas no se pueden acceder, todas las demás sí

Capacidad requerida para acceder a URLs internas
Algunas URLs se resuelven a direcciones internas (127.0.0.1, rangos link-local, endpoints de cloud-metadata, etc.) que pueden exponer servicios internos si se alcanzan. Este ajuste se configura en la página de Ajustes, bajo Plugin Configuration > HTTP Client.

Capacidad de WordPress que el usuario solicitante debe tener para apuntar a URLs que se resuelven a direcciones internas (127.0.0.1, rangos link-local, endpoints de cloud-metadata, etc.).
Por defecto es manage_options para que los usuarios no administradores no puedan alcanzar servicios internos a través de los campos del Cliente HTTP.
Selecciona (cualquier usuario con sesión iniciada) para deshabilitar la comprobación de capacidad.
Cuándo usar cada campo
Todos los campos son similares pero diferentes.
_sendJSONObjectItemHTTPRequest
Este campo obtiene un elemento de objeto JSON, lo cual es útil al consultar un solo elemento de un endpoint REST, como del endpoint de la WP REST API /wp-json/wp/v2/posts/1/.
Esta consulta:
{
postData: _sendJSONObjectItemHTTPRequest(input: { url: "https://newapi.getpop.org/wp-json/wp/v2/posts/1/" } )
}...obtiene esta respuesta:
{
"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 es similar a _sendJSONObjectItemHTTPRequest, pero obtiene una colección de objetos JSON, como del endpoint de la WP REST API /wp-json/wp/v2/posts/.
Esta consulta:
{
postData: _sendJSONObjectItemHTTPRequest(input: { url: "https://newapi.getpop.org/wp-json/wp/v2/posts/?per_page=3&_fields=id,type,title,date" } )
}...obtiene esta respuesta:
{
"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 obtiene un objeto HTTPResponse con todas las propiedades de la respuesta, de modo que podemos consultar independientemente el body (que es de tipo String, es decir, no se castea como JSON), el código de estado, content type y headers.
Por ejemplo, la siguiente consulta:
{
_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")
}
}...trae esta respuesta:
{
"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
Ejecutando la siguiente consulta:
{
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]
}
]
}
)
}...trae la siguiente respuesta:
{
"data": {
"graphQLRequest": {
"data": {
"posts": [
{
"id": 1499,
"title": "COPE with WordPress: Post demo containing plenty of blocks"
},
{
"id": 1,
"title": "Hello world!"
}
]
}
}
}
}Campos de múltiples peticiones: _sendJSONObjectItemHTTPRequests, _sendJSONObjectCollectionHTTPRequests, _sendGraphQLHTTPRequests y _sendHTTPRequests
Estos campos funcionan de forma similar a sus correspondientes campos no múltiples, pero obtienen datos de varios endpoints a la vez, ya sea de forma asíncrona (en paralelo) o síncrona (uno tras otro). Las respuestas se colocan en una lista, en el mismo orden en que se definieron las URLs en el parámetro urls.
Por ejemplo, la siguiente consulta:
{
weatherForecasts: _sendJSONObjectItemHTTPRequests(
urls: [
"https://api.weather.gov/gridpoints/TOP/31,80/forecast",
"https://api.weather.gov/gridpoints/TOP/41,55/forecast"
]
)
}...produce esta respuesta:
{
"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
}
}
}
]
}
}Ejecución síncrona vs asíncrona
Estos campos nos permiten ejecutar múltiples peticiones:
_sendHTTPRequests_sendJSONObjectItemHTTPRequests_sendJSONObjectCollectionHTTPRequests_sendGraphQLHTTPRequests
Estos campos reciben el input $async, para definir si las peticiones deben ejecutarse de forma síncrona ($async => false) o asíncrona.
Ejecución síncrona
Las peticiones HTTP se ejecutan en orden, cada una ejecutándose justo después de que la anterior se haya resuelto.
Cuando todas las peticiones HTTP son exitosas, el campo imprimirá un array con sus respuestas, en el mismo orden en que aparecen en la lista de entrada.
Si alguna petición HTTP falla, la ejecución se detiene justo ahí, es decir, las peticiones HTTP posteriores en la lista de entrada no se ejecutan.
Algunas posibles causas de peticiones HTTP fallidas son:
- El servidor al que conectarse está offline
- El código de estado de la respuesta no es 200: un 500 error interno, un 404 no encontrado, un 403 prohibido, etc.
- El content type de la respuesta no es
application/json
(Estos dos últimos son tratados como un error por _sendJSONObjectItemHTTPRequests, _sendJSONObjectCollectionHTTPRequests y _sendGraphQLHTTPRequests, que esperan manejar solo tipos JSON, pero no por _sendHTTPRequests, que no es opinado.)
En caso de error, el campo devuelve null (es decir, la respuesta de cualquier petición HTTP exitosa previa no se imprimirá), y la entrada de error contendrá la extensión httpRequestInputArrayPosition para indicar cuál es el elemento de la lista de entrada que falló (empezando desde 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
}
}Ejecución asíncrona
Todas las peticiones HTTP se ejecutan concurrentemente (es decir, en paralelo), y no se conoce en qué orden se resolverán las peticiones HTTP.
Cuando todas las peticiones HTTP son exitosas, el campo imprimirá un array con sus respuestas, en el mismo orden en que aparecen en la lista de entrada.
Cada vez que falle alguna petición HTTP, la ejecución se detiene inmediatamente, sin embargo para entonces puede que todas las demás peticiones HTTP también se hayan ejecutado.
Además, el servidor no indicará cuál es el elemento de la lista que falló (observa que no hay extensión httpRequestInputArrayPosition en la respuesta de abajo):
{
"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 Globales
Todos estos campos son Global Fields, por lo que se añaden a todos y cada uno de los tipos del esquema GraphQL: en QueryRoot, pero también en Post, User, Comment, etc.
Esto nos permite conectar con algún endpoint de API externa generado en tiempo de ejecución en la misma consulta GraphQL, basándonos en los datos almacenados en alguna entidad.
Por ejemplo, podemos iterar una lista de usuarios en nuestra base de datos y, para cada uno, conectarnos a un sistema externo (como un CRM) para obtener más datos sobre ellos.
En esta consulta, generamos el endpoint de la API utilizando la funcionalidad Field to Input y el 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 } )
}
}...produciendo:
{
"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"
}
]
}
}
}
]
}
}