从源码角度分析WordPress REST API路由注册与回调分发机制

好的,我们开始。

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;
}

这个方法的主要逻辑如下:

  1. 参数验证: 验证命名空间(namespace)、路由(route)和参数(args)是否有效。
  2. 方法规范化: 将 HTTP 方法(methods)转换为大写。
  3. 方法拆分: 如果指定了多个 HTTP 方法,则将其拆分成数组。
  4. 方法有效性验证: 确保 HTTP 方法是允许的(READABLE, CREATABLE, EDITABLE, DELETABLE, ALLMETHODS)。
  5. 冲突检测: 检查是否已经注册了相同的命名空间、路由和方法,如果已经注册且 overridefalse,则返回 false
  6. 存储路由信息: 将路由信息存储到 $this->endpoints 数组中。$this->endpointsWP_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 );
    }
}

这个方法的主要逻辑如下:

  1. rest_api_before_serve 钩子: 触发 rest_api_before_serve 钩子,允许在处理请求之前修改 $wp_rest_server 对象。
  2. WP_REST_Server::dispatch(): 调用 WP_REST_Server::dispatch() 方法来查找匹配的路由并执行相应的回调函数。
  3. rest_api_after_serve 钩子: 触发 rest_api_after_serve 钩子,允许在处理请求之后修改响应结果。
  4. WP_REST_Server::respond(): 调用 WP_REST_Server::respond() 方法来发送响应。
  5. 异常处理: 如果发生异常,则返回一个 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 );
}

这个方法的主要逻辑如下:

  1. 方法规范化: 将 HTTP 方法转换为大写。
  2. WP_REST_Server::match_request(): 调用 WP_REST_Server::match_request() 方法来查找匹配的路由。
  3. 权限检查: 调用 permission_callback 函数来检查用户是否有权限访问该路由。
  4. 回调函数验证: 验证回调函数是否有效。
  5. 执行回调函数: 调用回调函数来处理请求,并将 WP_REST_Request 对象作为参数传递给回调函数。
  6. 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 ) );
}

这个方法的主要逻辑如下:

  1. 方法规范化: 将 HTTP 方法转换为大写。
  2. 遍历所有已注册的路由: 遍历 $this->endpoints 数组,查找匹配的路由。
  3. 正则表达式匹配: 使用正则表达式来匹配请求的路径和路由。
  4. 方法匹配: 检查路由是否支持请求的 HTTP 方法。
  5. 提取参数: 从请求的路径中提取参数。
  6. 返回匹配的路由信息: 如果找到匹配的路由,则返回包含处理函数和参数的数组。
  7. 返回错误: 如果没有找到匹配的路由,则返回一个 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_callbacksanitize_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通过一系列验证,最终调用回调。

发表回复

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