好的,我们开始。
WordPress REST API 路由注册与回调分发机制源码分析
大家好,今天我们深入探讨 WordPress REST API 的核心机制:路由注册与回调分发。我们将从源码层面剖析其运作原理,理解 WordPress 如何将 HTTP 请求映射到相应的处理函数。
1. REST API 初始化
WordPress REST API 的初始化主要发生在 wp-includes/rest-api.php
文件中。rest_api_init
钩子是关键的入口点。
/**
* Fires when the REST API is initialized.
*
* @since 4.4.0
*/
add_action( 'rest_api_init', 'create_initial_rest_routes', 0 );
add_action( 'rest_api_init', 'rest_api_default_filters', 10 );
add_action( 'rest_api_init', 'register_initial_settings', 10 );
这里注册了三个重要的函数:
create_initial_rest_routes
: 注册默认的 REST API 路由,例如 posts, users, taxonomies 等。rest_api_default_filters
: 添加默认的 REST API 过滤器,用于数据处理和权限控制。register_initial_settings
: 注册与 REST API 相关的设置。
create_initial_rest_routes
函数会进一步调用其他函数来注册具体的路由,例如 register_rest_core()
。
2. 路由注册的核心:register_rest_route()
register_rest_route()
函数是注册 REST API 路由的核心函数。它的定义位于 wp-includes/rest-api.php
文件中。
/**
* Registers a REST route.
*
* @since 4.4.0
*
* @global WP_REST_Server $wp_rest_server REST server instance.
*
* @param string $namespace The namespace for the route.
* @param string $route The route to register.
* @param array $args Optional. An array of arguments for the route.
* See WP_REST_Server::register_route() for details.
* @param bool $override Optional. Whether to override existing routes with the same namespace and route.
* Default false.
* @return bool True on success, false on failure.
*/
function register_rest_route( $namespace, $route, $args = array(), $override = false ) {
global $wp_rest_server;
if ( ! $wp_rest_server instanceof WP_REST_Server ) {
return false;
}
return $wp_rest_server->register_route( $namespace, $route, $args, $override );
}
这个函数实际上是将路由注册的任务委托给了全局的 $wp_rest_server
对象,该对象是 WP_REST_Server
类的实例。WP_REST_Server::register_route()
方法才是真正执行路由注册的地方。
让我们深入 WP_REST_Server::register_route()
方法的源码,它位于 wp-includes/rest-api/class-wp-rest-server.php
文件中。
/**
* Registers a REST route.
*
* @param string $namespace The namespace for the route.
* @param string $route The route to register.
* @param array $args Optional. An array of arguments for the route.
* @param bool $override Optional. Whether to override existing routes with the same namespace and route.
* @return bool True on success, false on failure.
*/
public function register_route( $namespace, $route, $args = array(), $override = false ) {
$namespace = trim( $namespace, '/' );
$route = '/' . trim( $route, '/' );
// Normalize the route.
$route = preg_replace( '#/+#', '/', $route );
if ( empty( $namespace ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Namespace must be specified.' ), '4.4.0' );
return false;
}
if ( empty( $route ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Route must be specified.' ), '4.4.0' );
return false;
}
if ( ! is_array( $args ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Args must be an array.' ), '4.4.0' );
return false;
}
// Ensure that the supported methods are in uppercase.
if ( isset( $args['methods'] ) ) {
$args['methods'] = strtoupper( $args['methods'] );
}
// Allow multiple methods to be specified.
$methods = isset( $args['methods'] ) ? explode( '|', $args['methods'] ) : array( WP_REST_Server::READABLE );
foreach ( $methods as $method ) {
$method = strtoupper( trim( $method ) );
if ( ! in_array( $method, array( WP_REST_Server::READABLE, WP_REST_Server::CREATABLE, WP_REST_Server::EDITABLE, WP_REST_Server::DELETABLE, WP_REST_Server::ALLMETHODS ), true ) ) {
_doing_it_wrong( __FUNCTION__, sprintf( __( 'Method "%s" not allowed.' ), $method ), '4.4.0' );
return false;
}
if ( isset( $this->endpoints[ $namespace ][ $route ][ $method ] ) && ! $override ) {
return false;
}
$this->endpoints[ $namespace ][ $route ][ $method ] = $args;
}
return true;
}
这个方法的主要逻辑如下:
- 参数验证: 验证命名空间(
namespace
)、路由(route
)和参数(args
)是否有效。 - 方法规范化: 将 HTTP 方法(
methods
)转换为大写。 - 方法拆分: 如果指定了多个 HTTP 方法,则将其拆分成数组。
- 方法有效性验证: 确保 HTTP 方法是允许的(
READABLE
,CREATABLE
,EDITABLE
,DELETABLE
,ALLMETHODS
)。 - 冲突检测: 检查是否已经注册了相同的命名空间、路由和方法,如果已经注册且
override
为false
,则返回false
。 - 存储路由信息: 将路由信息存储到
$this->endpoints
数组中。$this->endpoints
是WP_REST_Server
类的一个私有属性,用于存储所有已注册的路由。
$this->endpoints
的数据结构大致如下:
$this->endpoints = array(
'my-plugin/v1' => array(
'/books' => array(
'GET' => array(
'callback' => 'my_plugin_get_books',
'permission_callback' => '__return_true', // 允许所有用户
'args' => array(),
),
'POST' => array(
'callback' => 'my_plugin_create_book',
'permission_callback' => 'my_plugin_check_permission',
'args' => array(
'title' => array(
'required' => true,
'type' => 'string',
'description' => '书名',
),
),
),
),
'/books/(?P<id>[d]+)' => array( // 带有参数的路由
'GET' => array(
'callback' => 'my_plugin_get_book',
'permission_callback' => '__return_true',
'args' => array(
'id' => array(
'required' => true,
'type' => 'integer',
'description' => '书的ID',
),
),
),
'PUT' => array(
'callback' => 'my_plugin_update_book',
'permission_callback' => 'my_plugin_check_permission',
'args' => array(
'id' => array(
'required' => true,
'type' => 'integer',
'description' => '书的ID',
),
'title' => array(
'type' => 'string',
'description' => '书名',
),
),
),
'DELETE' => array(
'callback' => 'my_plugin_delete_book',
'permission_callback' => 'my_plugin_check_permission',
'args' => array(
'id' => array(
'required' => true,
'type' => 'integer',
'description' => '书的ID',
),
),
),
),
),
);
路由注册参数详解 ($args
)
$args
参数是一个数组,用于定义路由的各种属性。以下是一些常用的属性:
参数名 | 类型 | 描述 |
---|---|---|
methods |
string | HTTP 方法,例如 GET , POST , PUT , DELETE 。可以使用 | 分隔多个方法,例如 'GET|POST' 。也可以使用 WP_REST_Server::READABLE , WP_REST_Server::CREATABLE , WP_REST_Server::EDITABLE , WP_REST_Server::DELETABLE , WP_REST_Server::ALLMETHODS 常量。 |
callback |
callable | 处理请求的回调函数。该函数接收一个 WP_REST_Request 对象作为参数,并返回一个 WP_REST_Response 对象、数组或 WP_Error 对象。 |
permission_callback |
callable | 权限检查的回调函数。该函数接收一个 WP_REST_Request 对象作为参数,并返回一个布尔值(true 表示允许访问,false 表示拒绝访问)或一个 WP_Error 对象。如果返回 WP_Error 对象,则会返回相应的错误信息。 |
args |
array | 一个数组,用于定义路由的参数。每个参数都应该是一个数组,包含参数的属性,例如 required , type , description 等。 |
路由参数 (args
) 属性详解
args
数组中的每个参数都可以设置以下属性:
属性名 | 类型 | 描述 |
---|---|---|
required |
boolean | 指定参数是否是必需的。如果设置为 true ,并且请求中缺少该参数,则会返回一个错误。 |
type |
string | 指定参数的类型。常用的类型包括 string , integer , boolean , number , array , object 。 |
description |
string | 参数的描述。 |
default |
mixed | 参数的默认值。如果请求中缺少该参数,则使用默认值。 |
sanitize_callback |
callable | 用于清理参数的回调函数。 |
validate_callback |
callable | 用于验证参数的回调函数。 |
3. 请求分发:WP_REST_Server::serve_request()
当 WordPress 接收到一个 REST API 请求时,WP_REST_Server::serve_request()
方法负责将请求分发到相应的处理函数。 这个方法也是位于 wp-includes/rest-api/class-wp-rest-server.php
文件中。
/**
* Serve a REST API request.
*
* @param string $path Path to the requested endpoint.
*/
public function serve_request( $path ) {
try {
/**
* Fires before serving a REST API request.
*
* Allows modification of the server object before serving a request.
*
* @since 4.4.0
*
* @param WP_REST_Server $this REST server instance.
*/
do_action( 'rest_api_before_serve', $this );
$result = $this->dispatch( $_SERVER['REQUEST_METHOD'], $path );
/**
* Fires after serving a REST API request.
*
* @since 4.4.0
*
* @param WP_REST_Response|WP_Error $result Result of the request.
* @param WP_REST_Server $this REST server instance.
*/
do_action( 'rest_api_after_serve', $result, $this );
$this->respond( $result );
} catch ( Exception $e ) {
$error_data = array( 'status' => 500 );
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
$error_data['error'] = $e->getMessage();
}
$error = new WP_Error( 'rest_server_error', __( 'REST Server encountered an error' ), $error_data );
$this->respond( $error );
}
}
这个方法的主要逻辑如下:
rest_api_before_serve
钩子: 触发rest_api_before_serve
钩子,允许在处理请求之前修改$wp_rest_server
对象。WP_REST_Server::dispatch()
: 调用WP_REST_Server::dispatch()
方法来查找匹配的路由并执行相应的回调函数。rest_api_after_serve
钩子: 触发rest_api_after_serve
钩子,允许在处理请求之后修改响应结果。WP_REST_Server::respond()
: 调用WP_REST_Server::respond()
方法来发送响应。- 异常处理: 如果发生异常,则返回一个
WP_Error
对象。
现在,我们深入 WP_REST_Server::dispatch()
方法的源码。
/**
* Dispatch a request to the first route that matches.
*
* @param string $method HTTP method to use for the request.
* @param string $path Path to the requested endpoint.
* @return WP_REST_Response|WP_Error Result of the request.
*/
protected function dispatch( $method, $path ) {
$method = strtoupper( $method );
if ( empty( $path ) ) {
return new WP_Error( 'rest_no_route', __( 'No route was found matching the URL and request method' ), array( 'status' => 404 ) );
}
$matching_handler = $this->match_request( $method, $path );
if ( is_wp_error( $matching_handler ) ) {
return $matching_handler;
}
$handler = $matching_handler['handler'];
$params = $matching_handler['params'];
$request = new WP_REST_Request( $method, $path );
$request->set_attributes( $handler );
$request->set_url_params( $params );
$defaults = array(
'methods' => $method,
'callback' => null,
'args' => array(),
'permission_callback' => '__return_true',
);
$handler = wp_parse_args( $handler, $defaults );
$permission = call_user_func( $handler['permission_callback'], $request );
if ( is_wp_error( $permission ) ) {
return $permission;
}
if ( ! $permission ) {
return new WP_Error( 'rest_forbidden', __( 'Sorry, you are not allowed to do that.' ), array( 'status' => rest_authorization_required_code() ) );
}
if ( ! is_callable( $handler['callback'] ) ) {
return new WP_Error( 'rest_invalid_handler', __( 'The handler for the route is invalid.' ), array( 'status' => 500 ) );
}
$response = call_user_func( $handler['callback'], $request );
return rest_ensure_response( $response );
}
这个方法的主要逻辑如下:
- 方法规范化: 将 HTTP 方法转换为大写。
WP_REST_Server::match_request()
: 调用WP_REST_Server::match_request()
方法来查找匹配的路由。- 权限检查: 调用
permission_callback
函数来检查用户是否有权限访问该路由。 - 回调函数验证: 验证回调函数是否有效。
- 执行回调函数: 调用回调函数来处理请求,并将
WP_REST_Request
对象作为参数传递给回调函数。 rest_ensure_response()
: 调用rest_ensure_response()
函数来确保响应是一个WP_REST_Response
对象。
现在,我们深入 WP_REST_Server::match_request()
方法的源码。
/**
* Match the request to a registered route.
*
* @param string $method HTTP method to use for the request.
* @param string $path Path to the requested endpoint.
* @return array|WP_Error Array containing the handler and parameters, or WP_Error if no route is found.
*/
protected function match_request( $method, $path ) {
$method = strtoupper( $method );
foreach ( $this->endpoints as $namespace => $routes ) {
foreach ( $routes as $route => $handlers ) {
$match = preg_match( '@^' . preg_replace( '#(/([^)]+))#', '(/?([^/]+))', $route ) . '$@i', $path, $matches );
if ( ! $match ) {
continue;
}
if ( empty( $handlers[ $method ] ) && empty( $handlers[ WP_REST_Server::ALLMETHODS ] ) ) {
continue;
}
if ( ! empty( $handlers[ $method ] ) ) {
$handler = $handlers[ $method ];
} else {
$handler = $handlers[ WP_REST_Server::ALLMETHODS ];
}
$params = array();
preg_match_all( '#(([^)]+))#', $route, $param_names );
if ( ! empty( $param_names[1] ) ) {
$param_names = $param_names[1];
array_shift( $matches );
foreach ( $param_names as $index => $name ) {
if ( ! isset( $matches[ $index + 1 ] ) ) {
continue;
}
$params[ $name ] = $matches[ $index + 1 ];
}
}
return array(
'handler' => $handler,
'params' => $params,
);
}
}
return new WP_Error( 'rest_no_route', __( 'No route was found matching the URL and request method' ), array( 'status' => 404 ) );
}
这个方法的主要逻辑如下:
- 方法规范化: 将 HTTP 方法转换为大写。
- 遍历所有已注册的路由: 遍历
$this->endpoints
数组,查找匹配的路由。 - 正则表达式匹配: 使用正则表达式来匹配请求的路径和路由。
- 方法匹配: 检查路由是否支持请求的 HTTP 方法。
- 提取参数: 从请求的路径中提取参数。
- 返回匹配的路由信息: 如果找到匹配的路由,则返回包含处理函数和参数的数组。
- 返回错误: 如果没有找到匹配的路由,则返回一个
WP_Error
对象。
4. 总结:路由注册与回调分发流程
以下表格总结了 WordPress REST API 路由注册与回调分发的流程:
步骤 | 函数 | 描述 |
---|---|---|
1. 初始化 | rest_api_init |
初始化 REST API,注册默认路由和过滤器。 |
2. 路由注册 | register_rest_route |
注册 REST API 路由。实际上调用 WP_REST_Server::register_route() 。 |
3. 路由注册(实际操作) | WP_REST_Server::register_route() |
验证路由参数,将路由信息存储到 $this->endpoints 数组中。 |
4. 请求处理 | WP_REST_Server::serve_request() |
处理 REST API 请求。 |
5. 路由分发 | WP_REST_Server::dispatch() |
查找匹配的路由,执行权限检查,调用回调函数。 |
6. 路由匹配 | WP_REST_Server::match_request() |
遍历已注册的路由,使用正则表达式匹配请求的路径和路由,提取参数。 |
示例:自定义 REST API 路由
以下示例展示了如何注册一个自定义的 REST API 路由:
add_action( 'rest_api_init', function () {
register_rest_route( 'my-plugin/v1', '/books', array(
'methods' => 'GET',
'callback' => 'my_plugin_get_books',
'permission_callback' => '__return_true',
) );
register_rest_route( 'my-plugin/v1', '/books/(?P<id>d+)', array(
'methods' => 'GET',
'callback' => 'my_plugin_get_book',
'permission_callback' => '__return_true',
'args' => array(
'id' => array(
'validate_callback' => 'is_numeric',
'sanitize_callback' => 'absint',
),
),
) );
} );
function my_plugin_get_books( WP_REST_Request $request ) {
// 获取所有书籍
$books = array(
array( 'id' => 1, 'title' => 'The Hitchhiker's Guide to the Galaxy' ),
array( 'id' => 2, 'title' => 'The Lord of the Rings' ),
);
return rest_ensure_response( $books );
}
function my_plugin_get_book( WP_REST_Request $request ) {
$id = $request['id'];
// 根据 ID 获取书籍
$books = array(
array( 'id' => 1, 'title' => 'The Hitchhiker's Guide to the Galaxy' ),
array( 'id' => 2, 'title' => 'The Lord of the Rings' ),
);
foreach ($books as $book) {
if ($book['id'] == $id) {
return rest_ensure_response( $book );
}
}
return new WP_Error( 'rest_book_not_found', 'Book not found', array( 'status' => 404 ) );
}
5. 深入理解核心机制
通过以上分析,我们可以总结出 WordPress REST API 路由注册与回调分发的核心机制:
- 集中式管理: 所有路由都注册到
$wp_rest_server
对象的$this->endpoints
属性中,方便统一管理。 - 正则表达式匹配: 使用正则表达式来匹配请求的路径和路由,灵活性高,可以支持复杂的路由规则。
- 钩子机制: 提供
rest_api_init
,rest_api_before_serve
,rest_api_after_serve
等钩子,允许开发者在不同的阶段修改 REST API 的行为。 - 权限控制: 通过
permission_callback
函数来控制用户对路由的访问权限,保证 API 的安全性。 - 参数验证和清理: 通过
validate_callback
和sanitize_callback
函数来验证和清理请求参数,防止恶意攻击。
6. 实际应用场景
理解 WordPress REST API 的路由注册与回调分发机制,可以帮助我们更好地开发自定义的 REST API 接口,例如:
- 自定义数据接口: 为自定义文章类型、自定义字段等创建 REST API 接口,方便前端应用获取数据。
- 插件集成: 将插件的功能通过 REST API 暴露出来,方便与其他系统集成。
- 移动应用开发: 为移动应用提供 REST API 接口,实现与 WordPress 网站的数据交互。
- headless WordPress 开发: 使用 WordPress 作为后端,通过 REST API 将数据提供给前端应用,实现前后端分离。
7. 进一步学习
- 阅读 WordPress REST API 官方文档:https://developer.wordpress.org/rest-api/
- 研究 WordPress 核心代码中 REST API 的实现:
wp-includes/rest-api.php
,wp-includes/rest-api/class-wp-rest-server.php
- 尝试开发自定义的 REST API 接口,加深理解。
核心流程概括
我们学习了REST API初始化的过程,路由实际上是存放在REST SERVER这个类中的,然后客户端的request通过一系列验证,最终调用回调。