剖析 WordPress `WP_REST_Server` 类的源码:如何通过 `$endpoints` 数组管理所有注册的 REST API 端点。

WordPress REST API 端点管理:WP_REST_Server 里的秘密花园

大家好,我是今天的 “WordPress REST API 探险队” 的领队,准备好深入 WP_REST_Server 的腹地,揭秘它如何管理那些花花绿绿的 REST API 端点了吗? 我们今天要讲的就是 WP_REST_Server 类的核心机制,特别是它那神奇的 $endpoints 数组。

1. 欢迎来到 WP_REST_Server 的世界

首先,我们需要知道 WP_REST_Server 是 WordPress REST API 的大脑。 它负责处理请求,路由到正确的处理函数,并返回响应。 想象一下它是一个超级调度员,确保每个 API 请求都能准确地到达目的地。

WP_REST_Server 类的主要职责包括:

  • 路由: 将 HTTP 请求映射到相应的处理程序(callback)。
  • 验证: 确保请求是有效的并且用户有权限访问请求的资源。
  • 序列化: 将数据转换为 JSON 格式返回给客户端。
  • 发现: 提供 API 的元数据,例如可用端点和支持的方法。

2. $endpoints 数组:端点的藏宝图

WP_REST_Server 类中,$endpoints 属性是一个非常关键的数组,它存储了所有注册的 REST API 端点的信息。 简单来说,它就是一个关联数组,键是 API 的路由(例如 /wp/v2/posts),值是该路由对应的所有可能的 HTTP 方法(GET, POST, PUT, DELETE 等)的处理函数信息。

我们可以用下面这个表格来表示 $endpoints 数组的结构:

路由 (Route) HTTP 方法 (Method) 处理函数信息 (Handler Info)
/wp/v2/posts GET array('callback' => 'get_posts', 'permission_callback' => 'check_permission', 'args' => array(...))
/wp/v2/posts POST array('callback' => 'create_post', 'permission_callback' => 'check_permission', 'args' => array(...))
/wp/v2/posts/(?P<id>[d]+) GET array('callback' => 'get_post', 'permission_callback' => 'check_permission', 'args' => array(...))
/wp/v2/posts/(?P<id>[d]+) PUT array('callback' => 'update_post', 'permission_callback' => 'check_permission', 'args' => array(...))
/wp/v2/posts/(?P<id>[d]+) DELETE array('callback' => 'delete_post', 'permission_callback' => 'check_permission', 'args' => array(...))

其中:

  • 路由 (Route):API 端点的 URL 路径,可以使用正则表达式来匹配动态参数,例如 (?P<id>[d]+) 用于匹配一个数字 ID。
  • HTTP 方法 (Method):HTTP 请求的方法,例如 GET、POST、PUT、DELETE 等。
  • 处理函数信息 (Handler Info):一个包含处理该请求所需信息的数组。 重要的键包括 callback(处理请求的回调函数),permission_callback(用于检查用户权限的回调函数),以及 args(定义请求参数的数组)。

3. 注册端点:register_route() 方法

现在,让我们看看如何使用 register_route() 方法将新的端点添加到 $endpoints 数组中。 这是 WordPress 注册 REST API 端点的核心方法。

/**
 * Registers a new route.
 *
 * @param string $namespace The namespace for the route.
 * @param string $route The route to register.
 * @param array  $args An array of arguments for the route.
 *
 * @return bool True if the route was registered successfully, false otherwise.
 */
public function register_route( $namespace, $route, $args ) {
    // Validate namespace and route
    if ( empty( $namespace ) || empty( $route ) ) {
        return false;
    }

    // Ensure namespace starts and ends with a slash
    $namespace = trim( $namespace, '/' );
    $namespace = '/' . $namespace . '/';

    $route = '/' . trim( $route, '/' );

    // Combine namespace and route
    $full_route = $namespace . $route;

    // Loop through the methods and register each one
    foreach ( $args as $method => $options ) {
        $method = strtoupper( $method );

        // Validate method
        if ( ! in_array( $method, $this->get_allowed_methods(), true ) ) {
            continue;
        }

        // Validate options
        if ( ! is_array( $options ) ) {
            continue;
        }

        // Store the handler information in the $endpoints array
        $this->endpoints[ $full_route ][ $method ] = $options;
    }

    return true;
}

让我们逐步分析这个方法:

  1. 验证参数: 首先,它会检查命名空间 (namespace) 和路由 (route) 是否为空。 如果为空,则注册失败。
  2. 格式化路由: 它会确保命名空间以斜杠开头和结尾,并确保路由以斜杠开头。
  3. 组合路由: 它将命名空间和路由组合成完整的路由 (full_route)。
  4. 遍历方法: 它遍历 $args 数组,该数组包含了不同 HTTP 方法的处理函数信息。
  5. 验证方法: 它检查 HTTP 方法是否是允许的方法之一(例如 GET, POST, PUT, DELETE)。
  6. 存储处理函数信息: 最后,它将处理函数信息存储到 $endpoints 数组中。

示例:

假设我们要注册一个端点,用于获取特定作者的文章列表:

add_action( 'rest_api_init', function () {
    register_rest_route( 'my-plugin/v1', '/authors/(?P<id>d+)/posts', array(
        'methods'  => 'GET',
        'callback' => 'get_author_posts',
        'args'     => array(
            'id' => array(
                'validate_callback' => 'is_numeric',
                'sanitize_callback' => 'absint',
            ),
        ),
    ) );
} );

function get_author_posts( $request ) {
    $author_id = $request['id'];

    $args = array(
        'author' => $author_id,
    );

    $posts = get_posts( $args );

    if ( empty( $posts ) ) {
        return new WP_Error( 'no_posts', 'No posts found for this author.', array( 'status' => 404 ) );
    }

    $data = array();
    foreach ( $posts as $post ) {
        $data[] = array(
            'id'    => $post->ID,
            'title' => $post->post_title,
            'content' => $post->post_content,
        );
    }

    return rest_ensure_response( $data );
}

在这个例子中,register_rest_route() 函数(它是 WP_REST_Server 的一个静态代理方法)会将以下信息存储到 $endpoints 数组中:

  • 路由: /my-plugin/v1/authors/(?P<id>d+)/posts
  • 方法: GET
  • 处理函数信息: 包含了 callbackget_author_posts 函数)和 args(定义了 id 参数的验证和清理规则)。

4. 路由匹配:找到你的“真命天子”端点

当收到一个 REST API 请求时,WP_REST_Server 需要找到与该请求匹配的端点。 这个过程涉及到遍历 $endpoints 数组,并使用正则表达式来匹配请求的 URL。

WP_REST_Server::match_request() 方法负责完成这个任务。 它会:

  1. 获取请求的 URL 和 HTTP 方法: 它会从请求中提取 URL 和 HTTP 方法。
  2. 遍历 $endpoints 数组: 它会遍历 $endpoints 数组中的每个路由。
  3. 使用正则表达式匹配路由: 它会使用 preg_match() 函数将请求的 URL 与 $endpoints 数组中的每个路由进行匹配。 如果匹配成功,它会提取 URL 中的参数。
  4. 检查 HTTP 方法: 它会检查请求的 HTTP 方法是否与匹配的路由支持的方法之一匹配。
  5. 返回匹配的端点信息: 如果找到匹配的端点,它会返回该端点的处理函数信息。

简化版代码示意 (仅为说明概念,与真实源码有差异):

public function match_request() {
    $request_uri = $_SERVER['REQUEST_URI'];
    $request_method = $_SERVER['REQUEST_METHOD'];

    foreach ( $this->endpoints as $route => $handlers ) {
        // 使用正则表达式匹配路由
        if ( preg_match( '#^' . $route . '$#', $request_uri, $matches ) ) {
            // 找到匹配的路由
            if ( isset( $handlers[ $request_method ] ) ) {
                // 找到匹配的 HTTP 方法
                $handler = $handlers[ $request_method ];

                // 提取 URL 参数
                $parameters = array();
                foreach ( $matches as $key => $value ) {
                    if ( is_string( $key ) ) {
                        $parameters[ $key ] = $value;
                    }
                }

                // 返回匹配的端点信息
                return array(
                    'callback' => $handler['callback'],
                    'parameters' => $parameters,
                );
            }
        }
    }

    // 没有找到匹配的端点
    return false;
}

5. 处理请求:回调函数的“舞台”

一旦 WP_REST_Server 找到了匹配的端点,它就会调用该端点对应的回调函数来处理请求。 回调函数负责执行实际的业务逻辑,例如从数据库中获取数据,更新数据,或删除数据。

WP_REST_Server::dispatch() 方法负责调用回调函数。 它会:

  1. 获取匹配的端点信息: 它会从 $endpoints 数组中获取匹配的端点信息。
  2. 检查权限: 它会调用 permission_callback 来检查用户是否有权限访问该端点。
  3. 调用回调函数: 它会调用 callback 函数来处理请求。
  4. 返回响应: 它会将回调函数的返回值转换为 JSON 格式,并返回给客户端。

简化版代码示意 (仅为说明概念,与真实源码有差异):

public function dispatch( $handler ) {
    // 检查权限
    if ( isset( $handler['permission_callback'] ) && ! call_user_func( $handler['permission_callback'] ) ) {
        // 没有权限
        return new WP_Error( 'rest_forbidden', 'You do not have permission to access this endpoint.', array( 'status' => 403 ) );
    }

    // 调用回调函数
    $response = call_user_func( $handler['callback'], $handler['parameters'] );

    // 返回响应
    return rest_ensure_response( $response );
}

6. 关于参数验证和清理:安全第一

REST API 的安全性至关重要。 WP_REST_Server 提供了参数验证和清理机制,以防止恶意用户提交恶意数据。

register_route() 方法中,我们可以使用 args 参数来定义请求参数的验证和清理规则。 例如:

register_rest_route( 'my-plugin/v1', '/items/(?P<id>d+)', array(
    'methods'  => 'POST',
    'callback' => 'update_item',
    'args'     => array(
        'id' => array(
            'validate_callback' => 'is_numeric',
            'sanitize_callback' => 'absint',
            'required'          => true,
        ),
        'title' => array(
            'sanitize_callback' => 'sanitize_text_field',
        ),
        'content' => array(
            'sanitize_callback' => 'wp_kses_post',
        ),
    ),
) );

在这个例子中:

  • validate_callback 用于验证参数是否有效。 如果验证失败,WP_REST_Server 会返回一个错误。
  • sanitize_callback 用于清理参数,以防止 XSS 攻击等安全问题。
  • required 用于指定参数是否是必须的。

常用的验证和清理函数包括:

  • is_numeric(): 验证参数是否是数字。
  • absint(): 将参数转换为绝对整数。
  • sanitize_text_field(): 清理文本字段,以防止 XSS 攻击。
  • wp_kses_post(): 允许在内容中使用安全的 HTML 标签。

7. 结语:驾驭 REST API 的力量

WP_REST_Server 类的 $endpoints 数组是 WordPress REST API 的核心。 通过理解它的结构和工作原理,我们可以更好地注册、管理和处理 REST API 端点。 掌握了这些知识,你就能像一位经验丰富的园丁一样,在 WordPress REST API 的花园里培育出各种各样的功能,让你的网站更加强大和灵活。

希望这次探险之旅对你有所帮助! 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注