分析 WordPress `rest_pre_dispatch` 钩子源码:在路由处理前执行自定义逻辑。

各位观众老爷,大家好!今天咱们来聊聊 WordPress 里一个相当重要的钩子——rest_pre_dispatch。这玩意儿就像个门卫,守在 REST API 的大门口,所有请求还没进门,就得先过它这关。咱们今天就扒一扒它的底裤,看看它到底能干些啥,怎么用它来实现一些骚操作。

一、啥是 REST API?为啥要有 rest_pre_dispatch

先简单科普一下 REST API。你可以把它想象成一个翻译官,让不同的应用程序(比如你的 WordPress 网站、手机 APP、甚至其他网站)能够用一种标准化的方式互相交流。WordPress 提供了自己的 REST API,让你可以通过 HTTP 请求来读取、创建、更新和删除 WordPress 的内容(文章、页面、用户等等)。

但是,问题来了。所有的请求都直接交给 WordPress 处理吗?如果我想在处理请求之前做一些事情,比如验证用户身份、记录日志、甚至直接返回一个错误,该怎么办呢? 这时候,rest_pre_dispatch 就闪亮登场了。

rest_pre_dispatch 钩子允许你在 REST API 的路由处理 之前 拦截请求,执行自定义逻辑,然后决定是否继续让 WordPress 处理这个请求。 这就像你在饭店门口安排了个保安,检查客人的着装、身份,或者直接拒绝某些不速之客入内。

二、rest_pre_dispatch 钩子的工作原理

rest_pre_dispatch 是一个 过滤器 钩子。这意味着它接受一个值作为参数,然后你可以对这个值进行修改,最后返回修改后的值。

  • 参数: 传递给 rest_pre_dispatch 的参数是 $result。这个 $result 可能是以下几种情况:

    • null:表示 WordPress 还没有决定如何处理这个请求。这是最常见的情况,也是我们最需要关注的情况。
    • WP_REST_Response 对象:表示某个钩子已经处理了这个请求,并且返回了一个响应。 此时,rest_pre_dispatch 应该直接返回这个 $result,不要再做任何修改。
    • WP_Error 对象:表示某个钩子在处理请求时发生了错误,并且返回了一个错误对象。 此时,rest_pre_dispatch 应该直接返回这个 $result,不要再做任何修改。
  • 返回值: rest_pre_dispatch 必须返回以下三种情况之一:

    • null:表示你没有处理这个请求,WordPress 应该继续寻找合适的路由来处理这个请求。
    • WP_REST_Response 对象:表示你已经处理了这个请求,并且返回了一个响应。 WordPress 将直接返回这个响应,不再继续寻找路由。
    • WP_Error 对象:表示在处理请求时发生了错误,并且返回了一个错误对象。 WordPress 将返回这个错误对象,不再继续寻找路由。

三、源码分析:一窥 rest_pre_dispatch 的真容

rest_pre_dispatch 钩子在 WordPress 核心代码的 WP_REST_Server::dispatch() 方法中被调用。 为了更清晰地展示代码结构,我将使用伪代码来简化说明,并重点关注 rest_pre_dispatch 的调用部分。

// 伪代码:WP_REST_Server::dispatch()

function dispatch( WP_REST_Request $request ) {
  // ... 一些代码 ...

  // 应用 rest_pre_dispatch 钩子
  $result = apply_filters( 'rest_pre_dispatch', null, $this, $request );

  // 检查 $result 的类型
  if ( null !== $result ) {
    // 如果 $result 不是 null,说明有钩子已经处理了请求
    return $result;
  }

  // ... 路由匹配和处理逻辑 ...

  // 如果没有找到匹配的路由,返回 404 错误
  return new WP_Error( 'rest_no_route', 'No route was found matching the URL and request method', array( 'status' => 404 ) );
}

从上面的伪代码可以看出:

  1. apply_filters( 'rest_pre_dispatch', null, $this, $request ); 这行代码是 rest_pre_dispatch 钩子的核心。 它会将 null 作为初始值,传递给所有注册到 rest_pre_dispatch 钩子的函数。 $thisWP_REST_Server 对象的实例,$requestWP_REST_Request 对象,包含了请求的所有信息。
  2. if ( null !== $result ) { ... } 这部分代码检查 rest_pre_dispatch 钩子是否返回了非 null 的值。 如果返回了 WP_REST_ResponseWP_Error 对象,则直接返回,不再进行后续的路由匹配和处理。

四、实战演练:用 rest_pre_dispatch 实现一些骚操作

现在,咱们来通过几个具体的例子,看看如何用 rest_pre_dispatch 来实现一些有用的功能。

例 1:权限验证

假设我们想限制只有登录用户才能访问某个特定的 REST API 接口。我们可以用 rest_pre_dispatch 来进行权限验证。

add_filter( 'rest_pre_dispatch', 'my_rest_authentication', 10, 3 );

function my_rest_authentication( $result, $server, $request ) {
  // 获取请求的路由
  $route = $request->get_route();

  // 判断是否需要进行权限验证的路由
  if ( strpos( $route, '/my-plugin/v1/secure-endpoint' ) === 0 ) {
    // 检查用户是否登录
    if ( ! is_user_logged_in() ) {
      // 如果用户未登录,返回一个错误
      return new WP_Error( 'rest_not_logged_in', 'You are not currently logged in.', array( 'status' => 401 ) );
    }
  }

  // 如果不需要验证,或者用户已经登录,则返回 null,让 WordPress 继续处理请求
  return $result;
}

代码解释:

  1. add_filter( 'rest_pre_dispatch', 'my_rest_authentication', 10, 3 );: 这行代码将 my_rest_authentication 函数注册到 rest_pre_dispatch 钩子上。 10 是优先级,3 是函数接受的参数个数。
  2. $route = $request->get_route();: 获取请求的路由。 例如,如果请求的 URL 是 https://example.com/wp-json/my-plugin/v1/secure-endpoint,那么 $route 的值就是 /my-plugin/v1/secure-endpoint
  3. if ( strpos( $route, '/my-plugin/v1/secure-endpoint' ) === 0 ) { ... }: 判断是否需要进行权限验证的路由。 这里我们假设只有 /my-plugin/v1/secure-endpoint 这个路由需要验证。
  4. if ( ! is_user_logged_in() ) { ... }: 检查用户是否登录。
  5. return new WP_Error( 'rest_not_logged_in', 'You are not currently logged in.', array( 'status' => 401 ) );: 如果用户未登录,返回一个 WP_Error 对象,表示权限不足。 status 设置为 401,表示未经授权。
  6. return $result;: 如果不需要验证,或者用户已经登录,则返回 $result(通常是 null),让 WordPress 继续处理请求。

例 2:日志记录

我们可以用 rest_pre_dispatch 来记录每个 REST API 请求的详细信息,比如请求的 URL、方法、参数等等。

add_filter( 'rest_pre_dispatch', 'my_rest_logging', 10, 3 );

function my_rest_logging( $result, $server, $request ) {
  // 获取请求的 URL
  $url = $request->get_absolute_url();

  // 获取请求的方法
  $method = $request->get_method();

  // 获取请求的参数
  $params = $request->get_params();

  // 将请求信息写入日志文件
  error_log( 'REST API Request: ' . $method . ' ' . $url . ' ' . json_encode( $params ) );

  // 返回 null,让 WordPress 继续处理请求
  return $result;
}

代码解释:

  1. $url = $request->get_absolute_url();: 获取请求的完整 URL。
  2. $method = $request->get_method();: 获取请求的方法,比如 GETPOSTPUTDELETE 等等。
  3. $params = $request->get_params();: 获取请求的参数。
  4. error_log( 'REST API Request: ' . $method . ' ' . $url . ' ' . json_encode( $params ) );: 将请求信息写入 PHP 的错误日志。 你可以根据自己的需求,将日志写入到其他地方,比如数据库、文件等等。

例 3:修改请求参数

有时候,我们可能需要在请求到达路由处理函数之前,修改一些请求参数。 比如,将用户输入的参数进行清理,或者添加一些默认参数。

add_filter( 'rest_pre_dispatch', 'my_rest_parameter_modification', 10, 3 );

function my_rest_parameter_modification( $result, $server, $request ) {
  // 获取请求的路由
  $route = $request->get_route();

  // 判断是否需要修改参数的路由
  if ( strpos( $route, '/my-plugin/v1/需要修改参数的路由' ) === 0 ) {
    // 获取请求的参数
    $params = $request->get_params();

    // 修改参数
    if ( isset( $params['search'] ) ) {
      $params['search'] = sanitize_text_field( $params['search'] ); // 清理搜索字符串
    }

    // 将修改后的参数设置回请求对象
    $request->set_query_params( $params );  // 对于 GET 请求
    $request->set_body_params( $params );   // 对于 POST/PUT/PATCH 请求

  }

  // 返回 null,让 WordPress 继续处理请求
  return $result;
}

代码解释:

  1. $request->set_query_params( $params );$request->set_body_params( $params );: 这两个方法分别用于设置 GET 请求的查询参数和 POST/PUT/PATCH 请求的主体参数。 你需要根据请求的方法,选择合适的方法来设置参数。

五、注意事项:使用 rest_pre_dispatch 的一些坑

虽然 rest_pre_dispatch 功能强大,但是在使用时也要注意一些问题,避免掉坑里。

  • 性能问题: rest_pre_dispatch 会在每个 REST API 请求之前执行,所以你的代码必须足够高效,否则会影响网站的性能。
  • 冲突问题: 如果多个插件或主题都使用了 rest_pre_dispatch,可能会发生冲突。 建议使用不同的优先级,并仔细测试。
  • 错误处理:rest_pre_dispatch 中,一定要进行充分的错误处理,避免出现未知的错误导致 REST API 请求失败。
  • 不要过度使用: rest_pre_dispatch 适用于需要在所有 REST API 请求之前执行的通用逻辑。 如果你的逻辑只适用于某个特定的路由,最好直接在路由处理函数中实现。

六、总结:rest_pre_dispatch 的正确打开方式

rest_pre_dispatch 钩子是 WordPress REST API 中一个非常强大的工具,可以让你在请求到达路由处理函数之前,对请求进行各种各样的处理。 但是,在使用时也要注意性能、冲突、错误处理等问题。 只有正确地使用 rest_pre_dispatch,才能让你的 WordPress 网站更加安全、高效、可控。

功能 示例代码 说明
权限验证 php if ( ! is_user_logged_in() ) { return new WP_Error( 'rest_not_logged_in', '...', array( 'status' => 401 ) ); } 检查用户是否登录,如果未登录则返回错误。
日志记录 php error_log( 'REST API Request: ' . $method . ' ' . $url . ' ' . json_encode( $params ) ); 记录 REST API 请求的详细信息,例如请求方法、URL 和参数。
修改请求参数 php $params['search'] = sanitize_text_field( $params['search'] ); $request->set_query_params( $params ); 修改请求的参数,例如对用户输入进行清理或添加默认参数。
提前返回数据 php return new WP_REST_Response( array( 'message' => '提前返回的数据' ), 200 ); 直接返回一个 REST API 响应,阻止 WordPress 进行后续的路由匹配和处理。
IP 地址黑名单 php $ip = $_SERVER['REMOTE_ADDR']; if ( in_array( $ip, $blacklist ) ) { return new WP_Error( 'rest_ip_blocked', '...', array( 'status' => 403 ) ); } 检查请求的 IP 地址是否在黑名单中,如果在则返回错误。
限制请求频率 (需要额外的存储机制,例如 Transient API 或数据库) 限制特定 IP 地址或用户的请求频率,防止恶意攻击。
多语言支持 php global $sitepress; if ( $sitepress ) { $lang = $request->get_param( 'lang' ); if ( $lang ) { $sitepress->switch_lang( $lang ); } } 根据请求的参数切换网站的语言 (需要 WPML 或类似的插件)。
调试模式 php if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( 'REST API Request: ' . print_r( $request, true ) ); } 在调试模式下,记录请求的详细信息,方便调试。

希望今天的讲座能够帮助大家更好地理解和使用 rest_pre_dispatch 钩子。 咱们下期再见!

发表回复

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