分析 `WP_REST_Server` 类的源码,解释它是如何处理 REST API 请求和路由的。

各位观众,晚上好!我是今晚的导游,将带大家深入探险 WordPress REST API 的核心——WP_REST_Server 类。准备好了吗?系好安全带,我们要出发了!

第一站:WP_REST_Server 是个啥?

简单来说,WP_REST_Server 就是 WordPress REST API 的大脑和神经中枢。它负责接收 HTTP 请求,解析请求信息,找到对应的处理函数(也就是路由),然后执行函数并返回结果。 你可以把它想象成一个超级厉害的总机接线员,负责把各种电话(HTTP 请求)转接到正确的人(处理函数)那里。

第二站:请求接收与解析

WP_REST_Server 的生命之旅始于 serve_request() 方法。这个方法是整个 REST API 的入口点,它会接收到 WordPress 丢过来的 HTTP 请求。

public function serve_request( $path = null ) {
    // 初始化请求方法
    $this->method = $this->get_method();

    // 设置请求路径
    if ( null === $path ) {
        $path = $this->get_rest_url();
    }

    // 解析请求
    $result = $this->dispatch( $this->parse_request( $path ) );

    // 发送响应
    $this->send_response( $result );
}
  • $this->get_method():这个方法用来获取 HTTP 请求的方法 (GET, POST, PUT, DELETE 等等)。
  • $this->get_rest_url():获取 REST API 的根 URL。
  • $this->parse_request():这个方法是关键,它负责解析请求,提取出请求的路径、参数等信息,并将这些信息封装成一个 WP_REST_Request 对象。
  • $this->dispatch():根据解析后的请求,找到对应的处理函数并执行。
  • $this->send_response():将处理函数返回的结果格式化并发送给客户端。

重点剖析:parse_request()

parse_request() 方法的作用是将 URL 分解成易于理解的片段。

public function parse_request( $path ) {
    $request = new WP_REST_Request( $this->method );

    // 从 URL 中提取路径参数
    $path = trim( $path, '/' );
    $path = explode( '/', $path );

    $route = array_shift( $path );

    // 从查询字符串中提取参数
    $params = $_GET;

    // 将路径参数和查询字符串参数合并到请求对象中
    $request->set_route( $route );
    $request->set_url_params( $path );
    $request->set_query_params( $params );

    // 处理请求体
    $body = $this->get_raw_data();
    if ( $body ) {
        $request->set_body( $body );
    }

    // 处理上传文件
    $files = $_FILES;
    if ( $files ) {
        $request->set_file_params( $files );
    }

    //... 其他处理逻辑,例如处理请求头等

    return $request;
}

简单来说,这个方法做了以下事情:

  1. 创建一个 WP_REST_Request 对象,用于存储请求的所有信息。
  2. 从 URL 中提取路由(route),也就是 API 的端点。例如,如果 URL 是 /wp-json/my-plugin/v1/items/123,那么路由就是 my-plugin/v1/items
  3. 从 URL 中提取路径参数(url params)。 例如,在上面的例子中,123 可能会被解析成 id123 的参数。
  4. 从查询字符串(query string)中提取参数。例如,如果 URL 是 /wp-json/my-plugin/v1/items?status=published&per_page=10,那么就会提取出 statusper_page 两个参数。
  5. 处理请求体(body),也就是 POST 或 PUT 请求中发送的数据。
  6. 处理上传的文件。

最终,parse_request() 方法会返回一个包含了所有请求信息的 WP_REST_Request 对象。

第三站:路由注册与匹配

路由(route)是 REST API 的核心概念之一。它定义了 API 的端点以及处理该端点的函数。WP_REST_Server 使用 $this->endpoints 数组来存储所有已注册的路由。

注册路由:register_route()

要注册一个路由,你需要使用 register_route() 方法。

public function register_route( $namespace, $route, $args = array(), $override = false ) {
    // 格式化路由参数
    $route = $this->normalize_route( $route );

    if ( ! isset( $this->endpoints[ $namespace ] ) ) {
        $this->endpoints[ $namespace ] = array();
    }

    if ( isset( $this->endpoints[ $namespace ][ $route ] ) && ! $override ) {
        // 路由已存在,且不允许覆盖
        return false;
    }

    // 将路由信息存储到 $this->endpoints 数组中
    $this->endpoints[ $namespace ][ $route ] = $args;

    return true;
}
  • $namespace:API 的命名空间。例如,my-plugin/v1
  • $route:API 的端点。例如,itemsitems/(?P<id>d+)
  • $args:一个数组,包含路由的处理函数、请求方法、权限验证回调函数等信息。
  • $override:一个布尔值,表示是否允许覆盖已存在的路由。

$args 数组的结构

$args 数组是路由注册时最重要的参数,它定义了如何处理该路由的请求。

$args = array(
    'methods'  => 'GET', // 或者 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'
    'callback' => 'my_plugin_get_items', // 处理请求的回调函数
    'permission_callback' => 'my_plugin_check_permission', // 权限验证回调函数
    'args'     => array( // 参数验证规则
        'id' => array(
            'validate_callback' => 'is_numeric',
            'sanitize_callback' => 'absint',
            'required'          => true,
        ),
    ),
);
  • methods:允许的 HTTP 请求方法。可以是单个方法,也可以是方法数组。
  • callback:处理请求的回调函数。这个函数接收一个 WP_REST_Request 对象作为参数,并返回一个 WP_REST_Response 对象或一个 WP_Error 对象。
  • permission_callback:权限验证回调函数。这个函数接收一个 WP_REST_Request 对象作为参数,并返回一个布尔值,表示当前用户是否有权限访问该路由。
  • args:一个数组,定义了请求参数的验证和清理规则。

匹配路由:match_request()

WP_REST_Server 接收到一个请求后,它会使用 match_request() 方法来找到对应的路由。

protected function match_request( $request ) {
    $route = $request->get_route();
    $namespace = $request->get_namespace();

    if ( ! isset( $this->endpoints[ $namespace ] ) ) {
        return null; // 命名空间不存在
    }

    $routes = $this->endpoints[ $namespace ];

    foreach ( $routes as $route_regex => $route_args ) {
        if ( preg_match( '@^' . $route_regex . '$@i', $route, $matches ) ) {
            // 路由匹配成功
            return array(
                'regex' => $route_regex,
                'args'  => $route_args,
                'matches' => $matches,
            );
        }
    }

    return null; // 没有找到匹配的路由
}

这个方法会遍历 $this->endpoints 数组,使用正则表达式来匹配请求的路由。如果找到匹配的路由,它会返回一个包含路由信息和匹配结果的数组。

第四站:请求分发与执行

找到匹配的路由后,WP_REST_Server 会使用 dispatch() 方法来执行处理函数。

public function dispatch( WP_REST_Request $request ) {
    try {
        $result = $this->handle_request( $request );

        // 将结果转换为 WP_REST_Response 对象
        if ( ! ( $result instanceof WP_REST_Response ) && ! ( $result instanceof WP_Error ) ) {
            $result = rest_ensure_response( $result );
        }

        return $result;

    } catch ( Exception $e ) {
        $error = new WP_Error(
            'rest_exception',
            $e->getMessage(),
            array( 'status' => 500 )
        );
        return $error;
    }
}
  • $this->handle_request():这个方法负责执行具体的处理函数。
  • rest_ensure_response():确保返回的结果是一个 WP_REST_Response 对象或一个 WP_Error 对象。
  • try...catch 块:用于捕获处理函数抛出的异常,并将其转换为一个 WP_Error 对象。

重点剖析:handle_request()

handle_request() 方法是请求分发的核心。

protected function handle_request( WP_REST_Request $request ) {
    $matched_route = $this->match_request( $request );

    if ( ! $matched_route ) {
        return new WP_Error(
            'rest_no_route',
            __( 'No route was found matching the URL and request method' ),
            array( 'status' => 404 )
        );
    }

    $route_args = $matched_route['args'];
    $matches = $matched_route['matches'];

    // 验证权限
    if ( isset( $route_args['permission_callback'] ) ) {
        $permission = call_user_func( $route_args['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() )
            );
        }
    }

    // 验证参数
    $params = $request->get_params();
    $validated_params = $this->validate_request_arg_params( $params, $route_args, $request );
    if ( is_wp_error( $validated_params ) ) {
        return $validated_params;
    }
    $request->set_attributes( array( 'args' => $validated_params ) );

    // 执行回调函数
    if ( isset( $route_args['callback'] ) ) {
        $response = call_user_func( $route_args['callback'], $request );
        return $response;
    }

    return new WP_Error(
        'rest_no_callback',
        __( 'Route matched, but no callback was specified.' ),
        array( 'status' => 500 )
    );
}

这个方法做了以下事情:

  1. 找到匹配的路由。
  2. 验证权限。如果路由定义了 permission_callback,则会调用该函数来检查当前用户是否有权限访问该路由。
  3. 验证参数。如果路由定义了 args,则会根据 args 中定义的规则来验证请求参数。
  4. 执行回调函数。如果路由定义了 callback,则会调用该函数来处理请求。

参数验证:validate_request_arg_params()

validate_request_arg_params() 方法用于验证请求参数是否符合路由定义中 args 的规则。

protected function validate_request_arg_params( $params, $route_args, $request ) {
    $validated_params = array();

    if ( ! isset( $route_args['args'] ) ) {
        return $params; // 没有定义参数验证规则
    }

    $arg_defs = $route_args['args'];

    foreach ( $arg_defs as $arg_name => $arg_def ) {
        if ( ! isset( $params[ $arg_name ] ) ) {
            if ( isset( $arg_def['required'] ) && $arg_def['required'] ) {
                return new WP_Error(
                    'rest_missing_callback_param',
                    sprintf( __( 'Missing parameter %s' ), $arg_name ),
                    array( 'status' => 400, 'params' => array( $arg_name ) )
                );
            }
            continue; // 参数不存在,且不是必需的
        }

        $param_value = $params[ $arg_name ];

        // 验证参数
        if ( isset( $arg_def['validate_callback'] ) ) {
            $is_valid = call_user_func( $arg_def['validate_callback'], $param_value, $request, $arg_name );
            if ( is_wp_error( $is_valid ) ) {
                return $is_valid;
            }
            if ( ! $is_valid ) {
                return new WP_Error(
                    'rest_invalid_param',
                    sprintf( __( 'Invalid parameter %s' ), $arg_name ),
                    array( 'status' => 400, 'params' => array( $arg_name ) )
                );
            }
        }

        // 清理参数
        if ( isset( $arg_def['sanitize_callback'] ) ) {
            $param_value = call_user_func( $arg_def['sanitize_callback'], $param_value, $request, $arg_name );
        }

        $validated_params[ $arg_name ] = $param_value;
    }

    return $validated_params;
}

这个方法会遍历路由定义中 args 的每一个参数,并执行以下操作:

  1. 检查参数是否存在。如果参数是必需的,但请求中没有提供该参数,则会返回一个 WP_Error 对象。
  2. 验证参数。如果参数定义了 validate_callback,则会调用该函数来验证参数的值。
  3. 清理参数。如果参数定义了 sanitize_callback,则会调用该函数来清理参数的值。

第五站:响应发送

dispatch() 方法执行完处理函数后,会将返回的结果传递给 send_response() 方法。

public function send_response( $response, $status = null, $headers = array() ) {
    // 处理 WP_Error 对象
    if ( is_wp_error( $response ) ) {
        $data = $response->get_error_data();
        if ( is_array( $data ) && isset( $data['status'] ) ) {
            $status = (int) $data['status'];
        } else {
            $status = 500;
        }

        $response = array(
            'code'    => $response->get_error_code(),
            'message' => $response->get_error_message(),
            'data'    => $data,
        );
    }

    // 确保响应是一个数组或对象
    if ( ! is_array( $response ) && ! is_object( $response ) ) {
        $response = array( 'message' => (string) $response );
    }

    // 设置 Content-Type 头部
    if ( ! headers_sent() ) {
        header( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ) );
    }

    // 设置状态码
    if ( null !== $status ) {
        status_header( $status );
    }

    // 设置其他头部
    foreach ( $headers as $key => $value ) {
        header( "$key: $value" );
    }

    // 将响应转换为 JSON 格式并输出
    echo wp_json_encode( $response );

    // 退出
    exit;
}

这个方法做了以下事情:

  1. 处理 WP_Error 对象。如果响应是一个 WP_Error 对象,则会提取错误信息,并设置 HTTP 状态码。
  2. 确保响应是一个数组或对象。
  3. 设置 Content-Type 头部为 application/json
  4. 设置 HTTP 状态码。
  5. 设置其他头部。
  6. 将响应转换为 JSON 格式并输出。
  7. 退出。

总结

WP_REST_Server 类是 WordPress REST API 的核心,它负责接收、解析、路由和分发 HTTP 请求,并将处理结果以 JSON 格式返回给客户端。理解 WP_REST_Server 类的工作原理,可以帮助你更好地使用和扩展 WordPress REST API。

表格总结

步骤 方法 描述
1. 接收请求 serve_request() 接收 HTTP 请求,设置请求方法和路径。
2. 解析请求 parse_request() 解析 URL 和请求体,提取路由、参数等信息,并封装成 WP_REST_Request 对象。
3. 路由注册 register_route() 注册 API 路由,定义端点、处理函数、权限验证等信息。
4. 路由匹配 match_request() 匹配请求的路由,找到对应的处理函数。
5. 请求分发 dispatch() 分发请求到对应的处理函数,执行权限验证、参数验证等操作。
6. 处理请求 handle_request() 执行具体的处理函数,并返回结果。
7. 参数验证 validate_request_arg_params() 验证请求参数是否符合路由定义中 args 的规则。
8. 发送响应 send_response() 将处理结果格式化为 JSON 格式,并发送给客户端。

希望这次旅行对大家有所帮助!下次再见!

发表回复

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