Batch (пакетный запрос)

В WordPress 5.6 появилась возможность выполнять серию REST API запросов в одном запросе к серверу. В самом простом виде это полезная оптимизация производительности, когда необходимо выполнить большое количество операций записи. Кроме того, опционально можно использовать базовые средства управления параллелизмом.

Регистрация

Чтобы использовать пакетный запрос, маршруты должны поддерживать эту функцию. Она включается при регистрации. Например:

register_rest_route( 'my-ns/v1', 'my-route', [
	'methods'             => WP_REST_Server::CREATABLE,
	'callback'            => 'my_callback',
	'permission_callback' => 'my_permission_callback',
	'allow_batch'         => [ 'v1' => true ],
] );

Если маршрут REST API был реализован с использованием лучших практик, декларирование поддержки должно быть достаточным для того, чтобы маршрут можно было записывать через конечную точку пакетной обработки. В частности, вот на что следует обратить внимание:

  • Маршруты должны использовать объект WP_REST_Request для получения всех данных запроса. Другими словами, он не должен обращаться к переменным $_GET, $_POST или $_SERVER для получения параметров или заголовков.

  • Маршруты должны возвращать данные. Это может быть объект WP_REST_Response, объект WP_Error или любой вид сериализуемых данных JSON. Это означает, что маршрут не должен напрямую передавать вывод ответа или использовать die(). Например, с помощью wp_send_json() или wp_die().

  • Маршруты должны быть реентерабельными. Будьте готовы к тому, что один и тот же маршрут будет вызываться несколько раз в пакете.

Выполнение запроса

Чтобы отправить пакетный запрос, сделайте POST-запрос на /wp-json/batch/v1 с массивом нужных запросов. Например, простейший пакетный запрос выглядит следующим образом.

{
  "requests": [
	{
	  "path": "/my-ns/v1/route"
	}
  ]
}

Формат запроса

Каждый запрос - это объект, который может принимать следующие свойства.

{
  "method": "PUT",
  "path": "/my-ns/v1/route/1?query=param",
  "headers": {
	"My-Header": "my-value",
	"Multi": [ "v1", "v2" ]
  },
  "body": {
	"project": "Gutenberg"
  }
}
  • method - метод HTTP, который будет использоваться для запроса. Если метод опущен, используется метод POST.
  • path - маршрут REST API для вызова. Можно включить параметры запроса. Это свойство является обязательным.
  • headers - объект, содержащий имена заголовков и их значения. Если заголовок имеет несколько значений, его можно передать в виде массива.
  • body - это параметры, которые нужно передать маршруту. Заполняется в типе параметров POST.

Обнаружение максимальных запросов

По умолчанию REST API принимает до 25 запросов в одном пакете. Однако это значение является фильтруемым, поэтому его можно увеличивать или уменьшать в зависимости от ресурсов сервера.

function my_prefix_rest_get_max_batch_size() {
	return 50;
}

add_filter( 'rest_get_max_batch_size', 'my_prefix_rest_get_max_batch_size' );

Поэтому клиентам настоятельно рекомендуется сделать предварительный запрос, чтобы узнать лимит. Например, запрос OPTIONS к batch/v1 вернет следующий ответ.

{
  "namespace": "",
  "methods": ["POST"],
  "endpoints": [
	{
	  "methods": ["POST" ],
	  "args": {
		"validation": {
		  "type": "string",
		  "enum": ["require-all-validate", "normal" ],
		  "default": "normal",
		  "required": false
		},
		"requests": {
		  "type": "array",
		  "maxItems": 25,
		  "items": {
			"type": "object",
			"properties": {
			  "метод": {
				"type": "string",
				"enum": [ "POST", "PUT", "PATCH", "DELETE" ],
				"default": "POST"
			  },
			  "path": {
				"type": "string",
				"required": true
			  },
			  "body": {
				"type": "object",
				"properties": [],
				"additionalProperties": true
			  },
			  "headers": {
				"type": "object",
				"properties": [],
				"additionalProperties": {
				  "type": ["string", "array" ],
				  "items": {
					"type": "string"
				  }
				}
			  }
			}
		  },
		  "required": true
		}
	  }
	}
  ],
  "_links": {
	"self": [
	  {
		"href": "http://trunk.test/wp-json/batch/v1"
	  }
	]
  }
}

Ограничение задается в свойстве endpoints[0].args.requests.maxItems.

Формат ответов

Конечная точка пакетной обработки возвращает код состояния 207 и ответы на каждый запрос в том же порядке, в котором они были запрошены. Например:

{
  "responses": [
	{
	  "body": {
		"id": 1,
		"_links": {
		  "self": [
			{
			  "href": "http://trunk.test/wp-json/my-ns/v1/route/1"
			}
		  ]
		}
	  },
	  "status": 201,
	  "headers": {
		"Location": "http://trunk.test/wp-json/my-n1/v1/route/1",
		"Разрешить": "GET, POST"
	  }
	}
  ]
}

Внутри REST API оборачивает каждый ответ, прежде чем включить его в массив ответов.

Режимы проверки

По умолчанию каждый запрос обрабатывается изолированно. Это означает, что пакетный ответ может содержать несколько успешных и несколько неудачных запросов. Иногда хочется обработать пакет только в том случае, если все запросы валидны. Например, в Gutenberg мы не хотим сохранять некоторые пункты меню, в идеале должны быть сохранены все или ни одного.

Для этого REST API позволяет передавать режим проверки require-all-validate. Когда этот параметр установлен, REST API сначала проверит, что каждый запрос является валидным в соответствии с WP_REST_Request::has_valid_params() и WP_REST_Request::sanitize_params(). Если какой-либо запрос не прошел проверку, то вся партия будет отклонена.

В данном примере выполняется пакет из двух запросов, и второй запрос не прошел валидацию. Поскольку порядок ответов не отличается от порядка запросов, для указания, что запрос не прошел проверку, используется null.

{
  "failed": "validation",
  "responses": [
	null,
	{
	  "body": {
		"code": "error_code",
		"message": "Неверные данные запроса",
		"data": { "status": 400 }
	  },
	  "status": 400,
	  "headers": {}
	}
  ]
}

Примечание: Использование require-all-validate не гарантирует, что все запросы будут успешными. Обратный вызов маршрута все равно может вернуть ошибку.

Callback для валидации

Методы WP_REST_Request используют обратный вызов validate_callback и sanitize_callback, указанные для каждого параметра при регистрации маршрута. В большинстве случаев это означает проверку на основе схемы.

Любая проверка, выполненная внутри маршрута, например, в методе prepare_item_for_database, не приведет к отклонению пакета. Если это вызывает беспокойство, рекомендуется перенести как можно больше валидации в validate_callback для каждого отдельного параметра. Это может быть сделано, например, поверх существующей проверки на основе схемы.

'post' => array(
	'type' => 'integer',
	'minimum' => 1,
	'required' => true,
	'arg_options' => array(
		'validate_callback' => function ( $value, $request, $param ) {
			$valid = rest_validate_request_arg( $value, $request, $param );

			if ( is_wp_error( $valid ) ) {
				return $valid;
			}

			if ( ! get_post( $value ) || ! current_user_can( 'read_post', $value ) ) {
				return new WP_Error( 'invalid_post', __( 'Этого поста не существует.' ) );
			}

			return true;
		}
	)
)

Иногда при выполнении проверки требуется полный контекст запроса. Обычно такая проверка выполнялась в prepare_item_for_database, но в WordPress 5.6 появилась альтернатива. При регистрации маршрута теперь можно указать верхний уровень validate_callback. Он будет получать полный объект WP_REST_Request и может возвращать экземпляр WP_Error или false. Обратный вызов не будет выполнен, если проверка на уровне параметров не прошла успешно.

register_rest_route( 'my-ns/v1', 'route', array(
	'callback'            => '__return_empty_array',
	'permission_callback' => '__return_true',
	'validate_callback'   => function( $request ) {
		if ( $request['pass1'] !== $request['pass2'] ) {
			return new WP_Error(
				'passwords_must_match',
				__( 'Пароли должны совпадать.' ),
				array( 'status' => 400 )
			);
		}

		return true;
	}
) );

Примечание: проверка запроса происходит до проверки прав доступа. Имейте это в виду, когда будете думать, стоит ли переносить логику в validate_callback.

Ограничения

В настоящее время ни один из встроенных маршрутов не поддерживает пакетную обработку. Это будет добавлено в будущем релизе, скорее всего, начиная сразу с WordPress 5.7.

GET-запросы не поддерживаются. Разработчикам рекомендуется использовать ссылки и встраивание или параллельные запросы.

--

See: https://make.wordpress.org/core/2020/11/20/rest-api-batch-framework-in-wordpress-5-6/