深入理解 `wp_remote_get()` 函数的源码,它是如何封装 `WP_Http` 类并处理 HTTP 请求的?

各位观众老爷们,大家好!今天咱们来聊聊 WordPress 里一个非常低调但又至关重要的函数:wp_remote_get()。它就像个默默无闻的信使,穿梭在你的 WordPress 站点和遥远的服务器之间,帮你取回各种数据。

别看它名字简单,wp_remote_get() 实际上是 WordPress HTTP API 的冰山一角。它背后隐藏着一个强大的类:WP_Http。今天,咱们就一层层扒开它的源码,看看这个信使是怎么工作的,以及它是如何巧妙地封装 WP_Http 类来处理 HTTP 请求的。

wp_remote_get():一个友好的包装

首先,让我们来看看 wp_remote_get() 函数本身的代码(简化版,去掉了各种过滤器和错误处理,重点突出核心逻辑):

function wp_remote_get( $url, $args = array() ) {
    $http = _wp_http_get_object(); // 获取 WP_Http 实例
    return $http->get( $url, $args ); // 调用 WP_Http 对象的 get 方法
}

function _wp_http_get_object() {
    static $http;

    if ( is_null( $http ) ) {
        $http = new WP_Http();
    }

    return $http;
}

简单来说,wp_remote_get() 干了两件事:

  1. 获取 WP_Http 对象: 它通过 _wp_http_get_object() 函数获取一个 WP_Http 类的实例。这里用到了一个静态变量 $http,保证了在同一个请求周期内,只会创建一个 WP_Http 对象,节省资源。 这也是一种单例模式的简单实现。

  2. 调用 get() 方法: 它直接调用 WP_Http 对象的 get() 方法,并将 URL 和参数传递给它。

所以,真正的英雄是 WP_Http 类。wp_remote_get() 只是给它穿了一层友好的外衣,方便我们使用。

WP_Http 类:幕后英雄登场

WP_Http 类才是真正干活的。它负责建立连接、发送请求、接收响应,以及处理各种 HTTP 协议的细节。让我们深入 WP_Http 类,看看它的 get() 方法是如何工作的。

class WP_Http {
    public function get( $url, $args = array() ) {
        return $this->request( $url, wp_parse_args( $args, array( 'method' => 'GET' ) ) );
    }

    public function request( $url, $args = array() ) {
        $args = wp_parse_args( $args );

        // 一大堆参数处理、过滤器... 这里省略

        $transport = $this->_get_first_available_transport( $url, $args );

        if ( ! $transport ) {
            return new WP_Error( 'http_no_url', __( 'Invalid URL provided.' ) );
        }

        $response = $transport->request( $url, $args );

        // 一大堆响应处理、过滤器... 这里省略

        return $response;
    }

    private function _get_first_available_transport( $url, $args = array() ) {
        $transports = array(
            'curl'      => 'WP_Http_Curl',
            'streams'   => 'WP_Http_Streams',
            'fsockopen' => 'WP_Http_Fsockopen',
        );
        // 根据环境判断依次使用什么方法
        foreach ( $transports as $key => $class ) {
            $transport = new $class();
            if ( $transport->test( $url, $args ) ) {
                return $transport;
            }
        }
        return false;
    }
}

WP_Http 类的 get() 方法,也没干什么特别的事情,它只是调用了 request() 方法,并设置了请求方法为 "GET"。真正的核心逻辑都在 request() 方法里。

request() 方法做了几件事:

  1. 参数解析: wp_parse_args() 函数将传入的参数和默认参数合并,确保参数的完整性。

  2. 选择传输方式: 这是关键的一步。_get_first_available_transport() 方法会根据当前服务器环境,选择一个可用的 HTTP 传输方式。常见的传输方式有:

    • cURL: 如果服务器安装了 cURL 扩展,WP_Http_Curl 类会被优先选择。cURL 是一个功能强大的 HTTP 客户端,支持各种协议和选项。
    • Streams: 如果 cURL 不可用,WP_Http_Streams 类会尝试使用 PHP 的 Streams API。Streams API 提供了一种更底层的 HTTP 通信方式。
    • fsockopen: 如果 Streams API 也不可用,WP_Http_Fsockopen 类会使用 fsockopen() 函数直接建立 socket 连接。这是最底层的 HTTP 通信方式。

    _get_first_available_transport() 会依次尝试这些传输方式,直到找到一个可用的为止。test() 方法用于判断当前传输方式是否可用。

  3. 发送请求: 一旦选择了传输方式,request() 方法会调用对应传输类的 request() 方法,发送 HTTP 请求。

  4. 处理响应: 接收到 HTTP 响应后,request() 方法会对响应进行处理,例如解析 HTTP 头、提取响应体等。

HTTP 传输类:各显神通

现在,让我们深入了解一下 WP_Http 类使用的三种 HTTP 传输类:WP_Http_CurlWP_Http_StreamsWP_Http_Fsockopen

WP_Http_Curl 类:cURL 大法好

如果服务器安装了 cURL 扩展,WP_Http_Curl 类通常是首选。cURL 是一个功能强大的 HTTP 客户端,支持各种协议和选项。

class WP_Http_Curl {
    public function test( $url = '', $args = array() ) {
        if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) ) {
            return false;
        }
        return true;
    }

    public function request( $url, $args = array() ) {
        $ch = curl_init();

        // 设置 cURL 选项
        curl_setopt( $ch, CURLOPT_URL, $url );
        curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); // 返回结果,不直接输出
        curl_setopt( $ch, CURLOPT_HEADER, true ); // 返回 header
        // ... 其他 cURL 选项 ...

        // 发送请求
        $response = curl_exec( $ch );

        // 处理响应
        $header_size = curl_getinfo( $ch, CURLINFO_HEADER_SIZE );
        $header = substr( $response, 0, $header_size );
        $body = substr( $response, $header_size );

        // ... 整理响应数据 ...

        curl_close( $ch );

        return $response_array;
    }
}

WP_Http_Curl 类的 request() 方法主要做了以下几件事:

  1. 初始化 cURL: curl_init() 函数初始化一个 cURL 会话。

  2. 设置 cURL 选项: curl_setopt() 函数用于设置 cURL 选项,例如:

    • CURLOPT_URL:设置请求的 URL。
    • CURLOPT_RETURNTRANSFER:设置是否返回结果,而不是直接输出。
    • CURLOPT_HEADER:设置是否返回 HTTP 头。
    • CURLOPT_POSTFIELDS:设置 POST 请求的数据。
    • CURLOPT_HTTPHEADER:设置自定义 HTTP 头。
    • CURLOPT_TIMEOUT:设置超时时间。
  3. 发送请求: curl_exec() 函数发送 HTTP 请求,并返回响应结果。

  4. 处理响应: curl_getinfo() 函数获取 cURL 会话的信息,例如 HTTP 状态码、响应头大小等。然后,将响应结果分割成 HTTP 头和响应体。

  5. 关闭 cURL 会话: curl_close() 函数关闭 cURL 会话,释放资源。

WP_Http_Streams 类:Streams API 的优雅

如果 cURL 不可用,WP_Http_Streams 类会尝试使用 PHP 的 Streams API。Streams API 提供了一种更底层的 HTTP 通信方式,但比直接使用 socket 连接更方便。

class WP_Http_Streams {
    public function test( $url = '', $args = array() ) {
        return function_exists( 'stream_socket_client' );
    }

    public function request( $url, $args = array() ) {
        // 构建 HTTP 请求头
        $request_header = '';

        // 构建 HTTP 请求体
        $request_body = '';

        // 创建 stream 上下文
        $context = stream_context_create( array(
            'http' => array(
                'method'  => $args['method'],
                'header'  => $request_header,
                'content' => $request_body,
                'timeout' => $args['timeout'],
            ),
        ) );

        // 发送请求
        $stream = @fopen( $url, 'r', false, $context );

        // 处理响应
        $response_header = stream_get_meta_data( $stream );
        $response_body = stream_get_contents( $stream );

        // ... 整理响应数据 ...

        fclose( $stream );

        return $response_array;
    }
}

WP_Http_Streams 类的 request() 方法主要做了以下几件事:

  1. 构建 HTTP 请求头和请求体: 根据传入的参数,构建 HTTP 请求头和请求体。

  2. 创建 stream 上下文: stream_context_create() 函数创建一个 stream 上下文,用于配置 HTTP 请求的各种选项,例如请求方法、HTTP 头、请求体、超时时间等。

  3. 发送请求: fopen() 函数使用 stream 上下文打开一个 URL,相当于发送 HTTP 请求。

  4. 处理响应: stream_get_meta_data() 函数获取 stream 的元数据,包括 HTTP 头。stream_get_contents() 函数获取 stream 的内容,即响应体。

  5. 关闭 stream: fclose() 函数关闭 stream,释放资源。

WP_Http_Fsockopen 类:Socket 连接的原始力量

如果 cURL 和 Streams API 都不可用,WP_Http_Fsockopen 类会使用 fsockopen() 函数直接建立 socket 连接。这是最底层的 HTTP 通信方式,需要手动处理 HTTP 协议的细节。

class WP_Http_Fsockopen {
    public function test( $url = '', $args = array() ) {
        return function_exists( 'fsockopen' );
    }

    public function request( $url, $args = array() ) {
        // 解析 URL
        $parsed_url = parse_url( $url );

        // 建立 socket 连接
        $host = $parsed_url['host'];
        $port = isset( $parsed_url['port'] ) ? $parsed_url['port'] : 80;
        $fp = fsockopen( $host, $port, $errno, $errstr, $args['timeout'] );

        // 构建 HTTP 请求
        $request = '';

        // 发送 HTTP 请求
        fwrite( $fp, $request );

        // 接收响应
        $response = '';
        while ( ! feof( $fp ) ) {
            $response .= fgets( $fp, 1160 );
        }

        // 处理响应
        // ... 整理响应数据 ...

        fclose( $fp );

        return $response_array;
    }
}

WP_Http_Fsockopen 类的 request() 方法主要做了以下几件事:

  1. 解析 URL: parse_url() 函数解析 URL,获取主机名、端口号等信息。

  2. 建立 socket 连接: fsockopen() 函数建立一个 socket 连接到指定的服务器。

  3. 构建 HTTP 请求: 手动构建 HTTP 请求,包括请求行、HTTP 头和请求体。

  4. 发送 HTTP 请求: fwrite() 函数将 HTTP 请求发送到服务器。

  5. 接收响应: fgets() 函数从 socket 连接中读取数据,直到读取完整个 HTTP 响应。

  6. 处理响应: 手动解析 HTTP 响应,包括 HTTP 状态码、HTTP 头和响应体。

  7. 关闭 socket 连接: fclose() 函数关闭 socket 连接,释放资源。

总结

现在,让我们回顾一下 wp_remote_get() 函数是如何封装 WP_Http 类并处理 HTTP 请求的:

步骤 函数/类 描述
1 wp_remote_get() 作为入口函数,接收 URL 和参数,并调用 WP_Http 类的 get() 方法。
2 WP_Http::get() 调用 WP_Http::request() 方法,并设置请求方法为 "GET"。
3 WP_Http::request() 解析参数,选择合适的 HTTP 传输方式(cURL、Streams API 或 socket 连接),并调用对应传输类的 request() 方法。
4 WP_Http_Curl::request() / WP_Http_Streams::request() / WP_Http_Fsockopen::request() 根据选择的传输方式,发送 HTTP 请求,接收 HTTP 响应,并处理响应数据。

wp_remote_get() 函数的设计充分考虑了服务器环境的兼容性。它会根据服务器是否安装了 cURL 扩展,以及是否支持 Streams API,自动选择合适的 HTTP 传输方式。这使得 WordPress 能够在各种服务器环境下正常工作。

wp_remote_get() 只是 WordPress HTTP API 的一个简单例子。WP_Http 类还提供了其他方法,例如 post()head()put()delete() 等,用于发送不同类型的 HTTP 请求。此外,WP_Http 类还提供了丰富的选项,用于配置 HTTP 请求的各种参数,例如超时时间、HTTP 头、代理服务器等。

掌握 wp_remote_get() 函数和 WP_Http 类的原理,可以帮助你更好地理解 WordPress 的 HTTP API,并在开发 WordPress 插件和主题时,更灵活地处理 HTTP 请求。

好了,今天的讲座就到这里。希望大家有所收获!下次再见!

发表回复

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