Http 客户端

class Cake\Http\Client(mixed $config = [])

CakePHP 包含一个 PSR-18 兼容的 HTTP 客户端,可用于发送请求。它是与 Web 服务和远程 API 通信的绝佳方式。

发送请求

发送请求简单直接。发送 GET 请求如下所示

use Cake\Http\Client;

$http = new Client();

// Simple get
$response = $http->get('http://example.com/test.html');

// Simple get with querystring
$response = $http->get('http://example.com/search', ['q' => 'widget']);

// Simple get with querystring & additional headers
$response = $http->get('http://example.com/search', ['q' => 'widget'], [
  'headers' => ['X-Requested-With' => 'XMLHttpRequest'],
]);

发送 POST 和 PUT 请求同样简单

// Send a POST request with application/x-www-form-urlencoded encoded data
$http = new Client();
$response = $http->post('http://example.com/posts/add', [
  'title' => 'testing',
  'body' => 'content in the post',
]);

// Send a PUT request with application/x-www-form-urlencoded encoded data
$response = $http->put('http://example.com/posts/add', [
  'title' => 'testing',
  'body' => 'content in the post',
]);

// Other methods as well.
$http->delete(/* ... */);
$http->head(/* ... */);
$http->patch(/* ... */);

如果您已创建 PSR-7 请求对象,则可以使用 sendRequest() 发送它

use Cake\Http\Client;
use Cake\Http\Client\Request as ClientRequest;

$request = new ClientRequest(
    'http://example.com/search',
    ClientRequest::METHOD_GET
);
$http = new Client();
$response = $http->sendRequest($request);

创建包含文件的 Multipart 请求

您可以通过在数组中包含文件句柄来在请求主体中包含文件

$http = new Client();
$response = $http->post('http://example.com/api', [
  'image' => fopen('/path/to/a/file', 'r'),
]);

文件句柄将被读取直到其结束;它在读取之前不会被倒带。

构建 Multipart 请求主体

有时您需要以非常特定的方式构建请求主体。在这种情况下,您通常可以使用 Cake\Http\Client\FormData 来制作您想要的特定 Multipart HTTP 请求

use Cake\Http\Client\FormData;

$data = new FormData();

// Create an XML part
$xml = $data->newPart('xml', $xmlString);
// Set the content type.
$xml->type('application/xml');
$data->add($xml);

// Create a file upload with addFile()
// This will append the file to the form data as well.
$file = $data->addFile('upload', fopen('/some/file.txt', 'r'));
$file->contentId('abc123');
$file->disposition('attachment');

// Send the request.
$response = $http->post(
    'http://example.com/api',
    (string)$data,
    ['headers' => ['Content-Type' => $data->contentType()]]
);

发送请求主体

在处理 REST API 时,您经常需要发送不是表单编码的请求主体。Http\Client 通过 type 选项公开这一点

// Send a JSON request body.
$http = new Client();
$response = $http->post(
  'http://example.com/tasks',
  json_encode($data),
  ['type' => 'json']
);

type 键可以是 ‘json’、‘xml’ 或完整 MIME 类型之一。在使用 type 选项时,您应该将数据提供为字符串。如果您正在进行需要查询字符串参数和请求主体的 GET 请求,您可以执行以下操作

// Send a JSON body in a GET request with query string parameters.
$http = new Client();
$response = $http->get(
  'http://example.com/tasks',
  ['q' => 'test', '_content' => json_encode($data)],
  ['type' => 'json']
);

请求方法选项

每个 HTTP 方法都接受一个 $options 参数,用于提供额外的请求信息。以下键可以在 $options 中使用

  • headers - 附加头的数组

  • cookie - 要使用的 Cookie 数组。

  • proxy - 代理信息的数组。

  • auth - 身份验证数据的数组,type 键用于委派给身份验证策略。默认情况下使用基本身份验证。

  • ssl_verify_peer - 默认值为 true。设置为 false 以禁用 SSL 证书验证(不建议)。

  • ssl_verify_peer_name - 默认值为 true。设置为 false 以在验证 SSL 证书时禁用主机名验证(不建议)。

  • ssl_verify_depth - 默认值为 5。在 CA 链中遍历的深度。

  • ssl_verify_host - 默认值为 true。将 SSL 证书验证到主机名。

  • ssl_cafile - 默认值为内置 cafile。覆盖以使用自定义 CA 捆绑包。

  • timeout - 超时前等待的持续时间(以秒为单位)。

  • type - 以自定义内容类型发送请求主体。需要 $data 作为字符串,或在进行 GET 请求时将 _content 选项设置为。

  • redirect - 要遵循的重定向次数。默认值为 false

  • curl - 额外的 curl 选项数组(如果使用 curl 适配器),例如,[CURLOPT_SSLKEY => 'key.pem']

options 参数始终是每个 HTTP 方法中的第三个参数。它们也可以在构造 Client 时用于创建 有范围的客户端

身份验证

Cake\Http\Client 支持几种不同的身份验证系统。开发人员可以添加不同的身份验证策略。身份验证策略在发送请求之前被调用,并允许在请求上下文中添加头。

使用基本身份验证

基本身份验证的示例

$http = new Client();
$response = $http->get('http://example.com/profile/1', [], [
  'auth' => ['username' => 'mark', 'password' => 'secret'],
]);

默认情况下,如果 auth 选项中没有 'type' 键,Cake\Http\Client 将使用基本身份验证。

使用摘要身份验证

基本身份验证的示例

$http = new Client();
$response = $http->get('http://example.com/profile/1', [], [
    'auth' => [
        'type' => 'digest',
        'username' => 'mark',
        'password' => 'secret',
        'realm' => 'myrealm',
        'nonce' => 'onetimevalue',
        'qop' => 1,
        'opaque' => 'someval',
    ],
]);

通过将 ‘type’ 键设置为 ‘digest’,您告诉身份验证子系统使用摘要身份验证。摘要身份验证支持以下算法

  • MD5

  • SHA-256

  • SHA-512-256

  • MD5-sess

  • SHA-256-sess

  • SHA-512-256-sess

算法将根据服务器挑战自动选择。

OAuth 1 身份验证

许多现代 Web 服务需要 OAuth 身份验证才能访问其 API。包含的 OAuth 身份验证假定您已经拥有消费者密钥和消费者密钥

$http = new Client();
$response = $http->get('http://example.com/profile/1', [], [
    'auth' => [
        'type' => 'oauth',
        'consumerKey' => 'bigkey',
        'consumerSecret' => 'secret',
        'token' => '...',
        'tokenSecret' => '...',
        'realm' => 'tickets',
    ],
]);

OAuth 2 身份验证

由于 OAuth2 通常是一个标头,因此没有专门的身份验证适配器。相反,您可以使用访问令牌创建客户端

$http = new Client([
    'headers' => ['Authorization' => 'Bearer ' . $accessToken],
]);
$response = $http->get('https://example.com/api/profile/1');

代理身份验证

一些代理需要身份验证才能使用它们。通常这种身份验证是基本身份验证,但可以通过任何身份验证适配器实现。默认情况下,Http\Client 将假定基本身份验证,除非设置了 type 键

$http = new Client();
$response = $http->get('http://example.com/test.php', [], [
    'proxy' => [
        'username' => 'mark',
        'password' => 'testing',
        'proxy' => '127.0.0.1:8080',
    ],
]);

第二个代理参数必须是一个字符串,其中包含 IP 或不带协议的域名。用户名和密码信息将通过请求头传递,而代理字符串将通过 stream_context_create() 传递。

创建作用域客户端

反复输入域名、身份验证和代理设置可能会很繁琐且容易出错。为了减少出错的机会并减轻一些繁琐的操作,您可以创建作用域客户端

// Create a scoped client.
$http = new Client([
    'host' => 'api.example.com',
    'scheme' => 'https',
    'auth' => ['username' => 'mark', 'password' => 'testing'],
]);

// Do a request to api.example.com
$response = $http->get('/test.php');

如果您的作用域客户端只需要来自 URL 的信息,您可以使用 createFromUrl()

$http = Client::createFromUrl('https://api.example.com/v1/test');

以上操作将创建一个客户端实例,其中设置了 protocolhostbasePath 选项。

创建作用域客户端时可以使用以下信息

  • host

  • basePath

  • scheme

  • proxy

  • auth

  • port

  • cookies

  • timeout

  • ssl_verify_peer

  • ssl_verify_depth

  • ssl_verify_host

在进行请求时,可以通过指定这些选项来覆盖任何选项。host、scheme、proxy、port 会在请求 URL 中被覆盖

// Using the scoped client we created earlier.
$response = $http->get('http://foo.com/test.php');

以上操作将替换域名、方案和端口。但是,此请求将继续使用在创建作用域客户端时定义的所有其他选项。有关支持选项的更多信息,请参见 请求方法选项

设置和管理 Cookie

Http\Client 在进行请求时也可以接受 Cookie。除了接受 Cookie 外,它还会自动存储响应中设置的有效 Cookie。任何带有 Cookie 的响应都会将其存储在 Http\Client 的原始实例中。存储在 Client 实例中的 Cookie 会自动包含在将来针对匹配域 + 路径组合的请求中

$http = new Client([
    'host' => 'cakephp.org'
]);

// Do a request that sets some cookies
$response = $http->get('/');

// Cookies from the first request will be included
// by default.
$response2 = $http->get('/changelogs');

您可以通过在请求的 $options 参数中设置 Cookie 来始终覆盖自动包含的 Cookie

// Replace a stored cookie with a custom value.
$response = $http->get('/changelogs', [], [
    'cookies' => ['sessionid' => '123abc'],
]);

您可以在创建客户端后使用 addCookie() 方法向客户端添加 Cookie 对象

use Cake\Http\Cookie\Cookie;

$http = new Client([
    'host' => 'cakephp.org'
]);
$http->addCookie(new Cookie('session', 'abc123'));

客户端事件

Client 在发送请求时会发出事件。在发送请求之前会触发 HttpClient.beforeSend 事件,在发送请求之后会触发 HttpClient.afterSend 事件。您可以在 beforeSend 监听器中修改请求或设置响应。对于所有请求都会触发 afterSend 事件,即使那些响应由 beforeSend 事件设置的请求也是如此。

响应对象

class Cake\Http\Client\Response

响应对象有许多用于检查响应数据的方法。

读取响应主体

您可以将整个响应主体作为字符串读取

// Read the entire response as a string.
$response->getStringBody();

您还可以访问响应的流对象并使用其方法

// Get a Psr\Http\Message\StreamInterface containing the response body
$stream = $response->getBody();

// Read a stream 100 bytes at a time.
while (!$stream->eof()) {
    echo $stream->read(100);
}

读取 JSON 和 XML 响应主体

由于 JSON 和 XML 响应是常用的,因此响应对象提供了一种使用访问器来读取解码数据的机制。JSON 数据被解码为数组,而 XML 数据被解码为 SimpleXMLElement

// Get some XML
$http = new Client();
$response = $http->get('http://example.com/test.xml');
$xml = $response->getXml();

// Get some JSON
$http = new Client();
$response = $http->get('http://example.com/test.json');
$json = $response->getJson();

解码后的响应数据存储在响应对象中,因此多次访问它没有任何额外的成本。

访问响应头

您可以通过几种不同的方法访问头。在通过方法访问头时,头名称始终被视为不区分大小写的值

// Get all the headers as an associative array.
$response->getHeaders();

// Get a single header as an array.
$response->getHeader('content-type');

// Get a header as a string
$response->getHeaderLine('content-type');

// Get the response encoding
$response->getEncoding();

检查状态代码

响应对象提供了一些用于检查状态代码的方法

// Was the response a 20x
$response->isOk();

// Was the response a 30x
$response->isRedirect();

// Get the status code
$response->getStatusCode();

更改传输适配器

默认情况下,Http\Client 将优先使用基于 curl 的传输适配器。如果 curl 扩展不可用,则会使用基于流的适配器。您可以使用构造函数选项强制选择传输适配器

use Cake\Http\Client\Adapter\Stream;

$http = new Client(['adapter' => Stream::class]);

事件

HTTP 客户端在发送请求之前和之后会触发一些事件,这使您能够修改请求或响应,或者执行其他任务,例如缓存、日志记录等。

HttpClient.beforeSend

// Somewhere before calling one of the HTTP client's methods which makes a request
$http->getEventManager()->on(
    'HttpClient.beforeSend',
    function (
        \Cake\Http\Client\ClientEvent $event,
        \Cake\Http\Client\Request $request,
        array $adapterOptions,
        int $redirects
    ) {
        // Modify the request
        $event->setRequest(....);
        // Modify the adapter options
        $event->setAdapterOptions(....);

        // Skip making the actual request by returning a response.
        // You can use $event->setResult($response) to achieve the same.
        return new \Cake\Http\Client\Response(body: 'something');
    }
);

HttpClient.afterSend

// Somewhere before calling one of the HTTP client's methods which makes a request
$http->getEventManager()->on(
    'HttpClient.afterSend',
    function (
        \Cake\Http\Client\ClientEvent $event,
        \Cake\Http\Client\Request $request,
        array $adapterOptions,
        int $redirects,
        bool $requestSent // Indicates whether the request was actually sent
                          // or response returned from ``beforeSend`` event
    ) {
        // Get the response
        $response = $event->getResponse();

        // Return a new/modified response.
        // You can use $event->setResult($response) to achieve the same.
        return new \Cake\Http\Client\Response(body: 'something');
    }
);

测试

trait Cake\Http\TestSuite\HttpClientTrait

在测试中,您通常希望为外部 API 创建模拟响应。您可以使用 HttpClientTrait 来定义对应用程序正在进行的请求的响应

use Cake\Http\TestSuite\HttpClientTrait;
use Cake\TestSuite\TestCase;

class CartControllerTests extends TestCase
{
    use HttpClientTrait;

    public function testCheckout()
    {
        // Mock a POST request that will be made.
        $this->mockClientPost(
            'https://example.com/process-payment',
            $this->newClientResponse(200, [], json_encode(['ok' => true]))
        );
        $this->post("/cart/checkout");
        // Do assertions.
    }
}

有一些方法可以模拟最常用的 HTTP 方法

$this->mockClientGet(/* ... */);
$this->mockClientPatch(/* ... */);
$this->mockClientPost(/* ... */);
$this->mockClientPut(/* ... */);
$this->mockClientDelete(/* ... */);
Cake\Http\TestSuite\HttpClientTrait::newClientResponse(int $code = 200, array $headers = [], string $body = '')

如上所示,您可以使用 newClientResponse() 方法为应用程序将要进行的请求创建响应。头需要是字符串列表

$headers = [
    'Content-Type: application/json',
    'Connection: close',
];
$response = $this->newClientResponse(200, $headers, $body)