概述
REST API 在许多方面非常简单。存在输入,称为请求。输入由服务器解释并生成输出。输出被称为响应。在某些方面,你可以将向 WordPress REST API 发出的请求视为一组应由 API 执行和解释的指令或说明。默认情况下,WordPress REST API 旨在使用 HTTP 请求作为其请求介质。HTTP 是互联网上数据通信的基础,这使得 WordPress REST API 成为一个非常广泛的 API。API 中的请求利用了 HTTP 请求中存在的许多不同方面,如 URI、HTTP 方法、标头和参数。请求的数据结构由 WP_REST_Request 类方便地处理。
WP_REST_Request
该类是 WordPress 4.4 中引入的三个主要基础设施类之一。当向 API 的端点发出 HTTP 请求时,API 将自动创建 WP_REST_Request 类的实例,以匹配提供的数据。响应对象在 WP_REST_Server 的 serve_request() 方法中自动生成。一旦创建了请求并检查了身份验证,请求就会被分发,我们的端点回调开始被触发。所有存储在 WP_REST_Request 对象中的数据都会传递给为我们注册的端点的回调函数。因此,我们的 permissions_callback 和 callback 都会在传入请求对象时被调用。这使得我们能够在回调中访问各种请求属性,从而定制我们的响应以匹配所需的输出。
请求属性
请求对象具有许多不同的属性,每个属性都可以以各种方式使用。主要属性是请求方法、路由、标头、参数和属性。让我们逐一分析它们在请求中的作用。如果你自己创建一个请求对象,它将如下所示:
$request = new WP_REST_Request( 'GET', '/my-namespace/v1/examples' );在上述代码示例中,我们仅指定请求对象的方法是 GET,并且我们应该匹配路由 /my-namespace/v1/examples,在完整 URL 的上下文中,它将如下所示:https://ourawesomesite.com/wp-json/my-namepsace/v1/examples。WP_REST_Request 构造函数的方法和路由参数用于将请求映射到所需的端点。如果请求发往未注册的端点,则会在响应中返回有用的 404 错误消息。让我们更深入地查看各种属性。
方法
请求对象的方法属性默认匹配 HTTP 请求方法。在大多数情况下,方法将是 GET、POST、PUT、DELETE、OPTIONS 或 HEAD 之一。这些方法将用于匹配注册到路由的各种端点。当 API 找到方法和路由的匹配项时,它将触发该端点的回调。
以下约定是匹配 HTTP 方法的最佳实践:GET 用于只读任务,POST 用于创建,PUT 用于更新,DELETE 用于删除。请求方法作为指示你的端点预期功能的标志。当你向路由发出 GET 请求时,你应该期望返回只读数据。
路由
请求的路由默认将匹配服务器环境变量中的路径信息;$_SERVER['PATH_INFO']。当你向 WordPress REST API 的路由发出 HTTP 请求时,生成的 WP_REST_Request 对象将被设置为匹配该路径,希望随后将其匹配到有效的端点。简而言之,请求的路由是你希望在 API 中针对请求的位置。
如果我们注册了一个使用 GET 的书籍端点,它可能位于 https://ourawesomesite.com/wp-json/my-namespace/v1/books。如果我们在浏览器中访问该 URL,我们将看到以 JSON 格式表示的书籍集合。WordPress 将自动为我们生成请求对象并处理所有路由以匹配端点。因此,由于我们不必担心路由本身,了解如何在请求中传递额外数据是更重要的事情。
标头
HTTP 请求标头只是关于我们 HTTP 请求的额外数据。请求标头可以指定缓存策略、我们的请求内容是什么、请求来自何处以及许多其他内容。请求标头不一定与我们的端点直接交互,但标头中的信息有助于 WordPress 知道该做什么。要传递我们希望端点与之交互的数据,我们想使用参数。
参数
在向 WordPress REST API 发出请求时,大多数传入的附加数据将采用参数的形式。什么是参数?在 API 的上下文中,有四种不同类型。有路由参数、查询参数、体参数和文件参数。让我们更深入地查看每一种。
URL 参数
URL 参数是从请求路由中的路径变量自动生成的 WP_REST_Request。这意味着什么?让我们看看这个路由,它通过 ID 获取单个书籍:/my-namespace/v1/books/(?P\d+)。看起来奇怪的 (?P\d+) 是一个路径变量。路径变量的名称是'id'。
如果我们发出像 GET https://ourawesomesite.com/wp-json/my-namespace/v1/books/5 这样的请求,5 将成为我们的 id 路径变量的值。WP_REST_Request 对象将自动采用该路径变量并将其存储为 URL 参数。现在在我们的端点回调内部,我们可以非常容易地与该 URL 参数交互。让我们看看一个示例。
// Register our individual books endpoint.
function prefix_register_book_route() {
register_rest_route( 'my-namespace/v1', '/books/(?P<id>\d+)', array(
// Supported methods for this endpoint. WP_REST_Server::READABLE translates to GET.
'methods' => WP_REST_Server::READABLE,
// Register the callback for the endpoint.
'callback' => 'prefix_get_book',
) );
}
add_action( 'rest_api_init', 'prefix_register_book_route' );
/**
* Our registered endpoint callback. Notice how we are passing in $request as an argument.
* By default, the WP_REST_Server will pass in the matched request object to our callback.
*
* @param WP_REST_Request $request The current matched request object.
*/
function prefix_get_book( $request ) {
// Here we are accessing the path variable 'id' from the $request.
$book = prefix_get_the_book( $request['id'] );
return rest_ensure_response( $book );
}
// A simple function that grabs a book title from our books by ID.
function prefix_get_the_book( $id ) {
$books = array(
'Design Patterns',
'Clean Code',
'Refactoring',
'Structure and Interpretation of Computer Programs',
);
$book = '';
if ( isset( $books[ $id ] ) ) {
// Grab the matching book.
$book = $books[ $id ];
} else {
// Error handling.
return new WP_Error( 'rest_not_found', esc_html__( 'The book does not exist', 'my-text-domain' ), array( 'status' => 404 ) );
}
return $book;
}在上述示例中,我们看到路径变量如何作为 URL 参数存储在请求对象中。然后我们可以在端点回调中访问这些参数。上述示例是使用 URL 参数的一个相当常见的用例。向路由添加太多路径变量会减慢路由匹配的速度,并且也会使注册端点过于复杂,建议谨慎使用 URL 参数。如果我们不应该在 URL 路径中直接使用参数,那么我们需要另一种方式将额外信息传递给请求。这就是查询和体参数发挥作用的地方,它们通常会在你的 API 中承担大部分工作。
查询参数
查询参数存在于 URI 的查询字符串部分。https://ourawesomesite.com/wp-json/my-namespace/v1/books?per_page=2&genre=fiction 的 URI 的查询字符串部分是 ?per_page=2&genre=fiction。查询字符串以'?'字符开头,查询字符串中的不同值由'&'字符分隔。我们在查询字符串中指定了两个参数;per_page 和 fiction。在我们的端点中,我们只想从虚构类中获取两本书。我们可以像这样在回调中访问这些值:$request['per_page'] 和 $request['genre'](假设 $request 是我们使用的参数的名称)。如果你熟悉 PHP,你可能已经在 Web 应用程序中使用过查询参数。
在 PHP 中,查询参数存储在超全局变量 $_GET 中。重要的是要注意,您永远不应直接在您的端点中访问任何超全局变量或服务器变量。最好始终使用 WP_REST_Request 类提供的功能。另一种向端点传递变量的常用方法是使用体参数。
体参数
体参数是存储在请求体中的键值对。如果您曾经通过 、cURL 或其他方法发送过 POST 请求,那么您就已经使用过体参数。使用体参数时,您可以以不同的内容类型传递它们。POST 请求的默认 Content-Type 标头是 x-www-form-urlencoded。当使用 x-www-form-urlencoded 时,参数的发送方式类似于查询字符串;per_page=2&genre=fiction。HTML 表单默认会将各种输入捆绑在一起并发送一个匹配 x-www-form-urlencoded 模式的 POST 请求。
重要的是要注意,虽然 HTTP 规范并不禁止在 GET 请求中使用体参数,但鼓励您不要在 GET 请求中使用体参数。体参数可以并且应该用于 POST、PUT 和 DELETE 请求。
文件参数
WP_REST_Request 对象中的文件参数在请求使用特殊内容类型标头时存储;multipart/form-data。然后可以使用 $request->get_file_params() 从请求对象访问文件数据。文件参数等同于 PHP 超全局变量:$_FILES。请记住,不要直接访问超全局变量,只使用 WP_REST_Request 对象提供的功能。
在端点回调中,我们可以使用 wp_handle_upload() 将所需的文件添加到 WordPress 的媒体上传目录。文件参数仅用于处理文件数据,您永远不应将其用于任何其他目的。
属性
WP_REST_Request 还支持请求属性。请求的属性是注册到匹配路由的路由属性。如果我们向 my-namespace/v1/books 发送一个 GET 请求,然后在端点回调中调用 $request->get_attributes(),我们将返回 my-namespace/v1/books 端点的注册选项。如果我们向同一路由发送 POST 请求,并且我们的端点回调也返回 $request->get_attributes(),我们将收到一组不同的注册到 POST 端点回调的端点选项。
在属性中,我们将获得一个包含支持的方法、选项、是否在此索引中显示此端点、端点的注册参数列表以及我们注册的回调的响应。它可能看起来像这样:
{
"methods": {
"GET": true
},
"accept_json": false,
"accept_raw": false,
"show_in_index": true,
"args": {
"context": {
"description": "请求所在的范围;确定响应中存在的字段。",
"type": "string",
"sanitize_callback": "sanitize_key",
"validate_callback": "rest_validate_request_arg",
"enum": [
"view",
"embed",
"edit"
],
"default": "view"
},
"page": {
"description": "集合的当前页。",
"type": "integer",
"default": 1,
"sanitize_callback": "absint",
"validate_callback": "rest_validate_request_arg",
"minimum": 1
},
"per_page": {
"description": "结果集中返回的最大项目数。",
"type": "integer",
"default": 10,
"minimum": 1,
"maximum": 100,
"sanitize_callback": "absint",
"validate_callback": "rest_validate_request_arg"
},
"search": {
"description": "将结果限制为匹配字符串的项目。",
"type": "string",
"sanitize_callback": "sanitize_text_field",
"validate_callback": "rest_validate_request_arg"
},
"after": {
"description": "将响应限制为在给定符合 ISO8601 的日期之后发布的资源。",
"type": "string",
"format": "date-time",
"validate_callback": "rest_validate_request_arg"
},
"author": {
"description": "将结果集限制为分配给特定作者的帖子。",
"type": "array",
"default": [],
"sanitize_callback": "wp_parse_id_list",
"validate_callback": "rest_validate_request_arg"
},
"author_exclude": {
"description": "确保结果集排除分配给特定作者的帖子。",
"type": "array",
"default": [],
"sanitize_callback": "wp_parse_id_list",
"validate_callback": "rest_validate_request_arg"
},
"before": {
"description": "将响应限制为在给定符合 ISO8601 的日期之前发布的资源。",
"type": "string",
"format": "date-time",
"validate_callback": "rest_validate_request_arg"
},
"exclude": {
"description": "确保结果集排除特定 ID。",
"type": "array",
"default": [],
"sanitize_callback": "wp_parse_id_list"
},
"include": {
"description": "将结果集限制为特定 ID。",
"type": "array",
"default": [],
"sanitize_callback": "wp_parse_id_list"
},
"offset": {
"description": "通过特定数量的项目偏移结果集。",
"type": "integer",
"sanitize_callback": "absint",
"validate_callback": "rest_validate_request_arg"
},
"order": {
"description": "按升序或降序对排序属性进行排序。",
"type": "string",
"default": "desc",
"enum": [
"asc",
"desc"
],
"validate_callback": "rest_validate_request_arg"
},
"orderby": {
"description": "按对象属性对集合进行排序。",
"type": "string",
"default": "date",
"enum": [
"date",
"relevance",
"id",
"include",
"title",
"slug"
],
"validate_callback": "rest_validate_request_arg"
},
"slug": {
"description": "将结果集限制为具有特定 slugs 的帖子。",
"type": "string",
"validate_callback": "rest_validate_request_arg"
},
"status": {
"default": "publish",
"description": "将结果集限制为分配了特定状态的帖子;可以是状态类型的逗号分隔列表。",
"enum": [
"publish",
"future",
"draft",
"pending",
"private",
"trash",
"auto-draft",
"inherit",
"any"
],
"sanitize_callback": "sanitize_key",
"type": "string",
"validate_callback": [
{},
"validate_user_can_query_private_statuses"
]
},
"filter": {
"description": "使用 WP 查询参数修改响应;私有查询变量需要适当的授权。"
},
"categories": {
"description": "将结果集限制为在分类法中分配了指定术语的所有项目。",
"type": "array",
"sanitize_callback": "wp_parse_id_list",
"default": []
},
"tags": {
"description": "将结果集限制为在标签分类法中分配了指定术语的所有项目。",
"type": "array",
"sanitize_callback": "wp_parse_id_list",
"default": []
}
},
"callback": [
{},
"get_items"
],
"permission_callback": [
{},
"get_items_permissions_check"
]
}如您所见,我们已注册到端点的所有信息都已在那里,随时可用!请求属性通常在较低级别使用,并由 WP_REST_Server 类处理,但在端点回调中可以做一些很酷的事情,例如限制接受的参数以匹配注册的参数。
WP REST API 的设计是为了让您无需折腾任何内部功能,因此与 WP_REST_Request 交互的一些更高级的方法并不常见。使用 WP REST API 的核心在于注册路由和端点。请求是我们用来告诉 API 我们要访问哪个端点的工具。这通常是通过 HTTP 完成的,但我们也可以在内部使用 WP_REST_Request。
内部请求
进行内部请求的关键是使用 rest_do_request()。您只需要传递一个请求对象,就会返回一个响应。由于请求从未由 WP_REST_Server 提供,因此响应数据永远不会编码为 json,这意味着我们拥有作为 PHP 对象的响应对象。这非常棒,使我们能够做很多有趣的事情。首先,我们可以创建高效的批量端点。从性能角度来看,其中一个障碍是最小化 HTTP 请求。我们可以创建批量端点,使用 rest_do_request() 在一个 HTTP 请求中内部提供所有请求。这是一个非常简化的只读数据批量端点,以便您可以看到 rest_do_request() 的作用。
// 注册我们的模拟批量端点。
function prefix_register_batch_route() {
register_rest_route( 'my-namespace/v1', '/batch', array(
// 此端点支持的方法。WP_REST_Server::READABLE 对应 GET。
'methods' => WP_REST_Server::READABLE,
// 注册端点的回调函数。
'callback' => 'prefix_do_batch_request',
// 注册批量端点的参数。
'args' => prefix_batch_request_parameters(),
) );
}
add_action( 'rest_api_init', 'prefix_register_batch_route' );
/**
* 我们注册的端点回调函数。注意我们如何将 $request 作为参数传入。
* 默认情况下,WP_REST_Server 会将匹配到的请求对象传递给我们的回调函数。
*
* @param WP_REST_Request $request 当前匹配到的请求对象。
*/
function prefix_do_batch_request( $request ) {
// 在此处初始化将存放响应数据的数组。
$data = array();
$data = prefix_handle_batch_requests( $request['requests'] );
return $data;
}
/**
* 此函数处理为我们发出的批量请求构建响应。
*
* @param array $requests 用于构建 WP_REST_Request 对象的数组数据。
* @return WP_REST_Response 批量端点的响应数据集合。
*/
function prefix_handle_batch_requests( $requests ) {
$data = array();
// 遍历 requests 参数中指定的每个请求并运行端点。
foreach ( $requests as $request_params ) {
$response = prefix_handle_request( $request_params );
$key = $request_params['method'] . ' ' . $request_params['route'];
$data[ $key ] = prefix_prepare_for_collection( $response );
}
return rest_ensure_response( $data );
}
/**
* 此函数处理为我们发出的批量请求构建响应。
*
* @param array $request_params 用于构建 WP_REST_Request 对象的数据。
* @return WP_REST_Response 请求的响应数据。
*/
function prefix_handle_request( $request_params ) {
$request = new WP_REST_Request( $request_params['method'], $request_params['route'] );
// 将指定的请求参数添加到请求中。
if ( isset( $request_params['params'] ) ) {
foreach ( $request_params['params'] as $param_name => $param_value ) {
$request->set_param( $param_name, $param_value );
}
}
$response = rest_do_request( $request );
return $response;
}
/**
* 准备响应以便插入到响应集合中。
*
* 此代码取自 WP REST API v2 插件中的 WP_REST_Controller 类。
*
* @param WP_REST_Response $response 响应对象。
* @return array 响应数据,已准备好插入到集合数据中。
*/
function prefix_prepare_for_collection( $response ) {
if ( ! ( $response instanceof WP_REST_Response ) ) {
return $response;
}
$data = (array) $response->get_data();
$server = rest_get_server();
if ( method_exists( $server, 'get_compact_response_links' ) ) {
$links = call_user_func( array( $server, 'get_compact_response_links' ), $response );
} else {
$links = call_user_func( array( $server, 'get_response_links' ), $response );
}
if ( ! empty( $links ) ) {
$data['_links'] = $links;
}
return $data;
}
/**
* 返回我们注册的参数的 JSON schema 数据。
*
* @return array $params JSON Schema 数据的 PHP 表示形式。
*/
function prefix_batch_request_parameters() {
$params = array();
$params['requests'] = array(
'description' => esc_html__( '一个请求对象参数数组,这些参数可以构建为 WP_REST_Request 实例。', 'my-text-domain' ),
'type' => 'array',
'required' => true,
'validate_callback' => 'prefix_validate_requests',
'items' => array(
array(
'type' => 'object',
'properties' => array(
'method' => array(
'description' => esc_html__( '所需请求的 HTTP 方法。', 'my-text-domain' ),
'type' => 'string',
'required' => true,
'enum' => array(
'GET',
'POST',
'PUT',
'DELETE',
'OPTIONS',
),
),
'route' => array(
'description' => esc_html__( '请求所需的路线。', 'my-text-domain' ),
'required' => true,
'type' => 'string',
'format' => 'uri',
),
'params' => array(
'description' => esc_html__( '所需请求参数的键值对。', 'my-text-domain' ),
'type' => 'object',
),
),
),
),
);
return $params;
}
function prefix_validate_requests( $requests, $request, $param_key ) {
// 如果 requests 不是请求数组,则不处理批量请求。
if ( ! is_array( $requests ) ) {
return new WP_Error( 'rest_invald_param', esc_html__( 'requests 参数必须是请求数组。', 'my-text-domain' ), array( 'status' => 400 ) );
}
foreach ( $requests as $request ) {
// 如果未设置方法或路线,则不运行请求。
if ( ! isset( $request['method'] ) || ! isset( $request['route'] ) ) {
return new WP_Error( 'rest_invald_param', esc_html__( '必须为每个请求指定方法和路线。', 'my-text-domain' ), array( 'status' => 400 ) );
}
if ( isset( $request['params'] ) && ! is_array( $request['params'] ) ) {
return new WP_Error( 'rest_invald_param', esc_html__( '必须为每个请求指定参数,格式为命名键值对数组。', 'my-text-domain' ), array( 'status' => 400 ) );
}
}
// 这是一种数据验证的黑名单方法。
return true;
}这是一大段涵盖多个主题的代码,但所有内容都围绕 prefix_handle_request() 中发生的事情展开。在此处,我们传入一个数组,该数组告诉我们 HTTP 方法、路线以及一组我们要将其转换为请求的参数。然后我们为方法和路线构建请求对象。如果指定了任何参数,我们使用 WP_REST_Request::set_param() 方法添加所需的参数。一旦我们的 WP_REST_Request 准备就绪,我们就使用 rest_do_request 在内部匹配该端点,并将响应返回到批量端点响应集合中。使用此类批量端点可以带来巨大的性能提升,因为您只需进行一次 HTTP 请求即可获取多个端点的响应。此实现不一定是最优的,仅作为示例;并非执行此操作的唯一方法。