请求和响应对象

请求和响应对象提供对 HTTP 请求和响应的抽象。CakePHP 中的请求对象允许您内省传入的请求,而响应对象允许您轻松地在控制器中创建 HTTP 响应。

请求

class Cake\Http\ServerRequest

ServerRequest 是 CakePHP 中使用的默认请求对象。它集中了许多用于查询和交互请求数据的功能。在每个请求中,都会创建一个请求对象,然后将其作为引用传递给使用请求数据的应用程序的各个层。默认情况下,请求被分配到 $this->request,并且在控制器、单元、视图和帮助程序中可用。您也可以使用控制器引用在组件中访问它。

在版本 4.4.0 中更改: 可以通过 DI 获取 ServerRequest。因此您可以从容器中获取它或将其用作服务的依赖项。

ServerRequest 执行的一些职责包括

  • 将 GET、POST 和 FILES 数组处理成您熟悉的数据结构。

  • 提供与请求相关的环境内省。例如,发送的头部、客户端的 IP 地址以及运行应用程序的服务器的子域/域名。

  • 提供对请求参数的访问,既可以通过数组索引访问,也可以通过对象属性访问。

CakePHP 的请求对象实现了 PSR-7 ServerRequestInterface,这使得更容易使用来自 CakePHP 外部的库。

请求参数

请求通过 getParam() 方法公开路由参数

$controllerName = $this->request->getParam('controller');

要将所有路由参数作为数组获取,请使用 getAttribute()

$parameters = $this->request->getAttribute('params');

所有 路由元素 都可以通过此接口访问。

除了 路由元素 之外,您通常还需要访问 传递参数。这些参数也都在请求对象上可用

// Passed arguments
$passedArgs = $this->request->getParam('pass');

将全部提供您对传递参数的访问权限。CakePHP 在内部使用了一些重要/有用的参数,这些参数也都在路由参数中找到

  • plugin 处理请求的插件。在没有插件的情况下将为 null。

  • controller 处理当前请求的控制器。

  • action 处理当前请求的操作。

  • prefix 当前操作的前缀。有关更多信息,请参见 前缀路由

查询字符串参数

Cake\Http\ServerRequest::getQuery($name, $default = null)

可以使用 getQuery() 方法读取查询字符串参数

// URL is /posts/index?page=1&sort=title
$page = $this->request->getQuery('page');

您可以直接访问查询属性,也可以使用 getQuery() 方法以无错误的方式读取 URL 查询数组。任何不存在的键都将返回 null

$foo = $this->request->getQuery('value_that_does_not_exist');
// $foo === null

// You can also provide default values
$foo = $this->request->getQuery('does_not_exist', 'default val');

如果您想访问所有查询参数,可以使用 getQueryParams()

$query = $this->request->getQueryParams();

可以使用类型转换实用程序函数对请求数据和其他输入进行类型安全访问

use function Cake\Core\toBool;
use function Cake\Core\toInt;
use function Cake\Core\toString;
use function Cake\I18n\toDate;
use function Cake\I18n\toDateTime;

// $active is bool|null.
$active = toBool($this->request->getQuery('active'));

// $page is int|null.
$page = toInt($this->request->getQuery('page'));

// $query is string|null.
$query = toString($this->request->getQuery('query'));

// Parse a date based on the format or null
$date = toDate($this->request->getQuery('date'), 'Y-m-d');

// Parse a datetime based on a format or null
$date = toDateTime($this->request->getQuery('datetime'), 'Y-m-d H:i:s');

在版本 5.1.0 中添加: 添加了类型转换函数。

请求主体数据

Cake\Http\ServerRequest::getData($name, $default = null)

可以通过 Cake\Http\ServerRequest::getData() 访问所有通常通过 PHP 的 $_POST 全局变量可用的 POST 数据。例如

// An input with a name attribute equal to 'title' is accessible at
$title = $this->request->getData('title');

您可以使用点分隔的名称来访问嵌套数据。例如

$value = $this->request->getData('address.street_name');

对于不存在的名称,将返回 $default

$foo = $this->request->getData('value.that.does.not.exist');
// $foo == null

您还可以使用 主体解析器中间件 将不同内容类型的请求主体解析成数组,以便可以通过 ServerRequest::getData() 访问。

如果您想访问所有数据参数,可以使用 getParsedBody()

$data = $this->request->getParsedBody();

文件上传

可以通过请求主体数据访问上传的文件,使用上面描述的 Cake\Http\ServerRequest::getData() 方法。例如,可以像这样访问具有 attachment 名称属性的输入元素中的文件

$attachment = $this->request->getData('attachment');

默认情况下,文件上传在请求数据中表示为实现 \Psr\Http\Message\UploadedFileInterface 的对象。在当前实现中,上面的示例中的 $attachment 变量默认情况下会保存 \Laminas\Diactoros\UploadedFile 的实例。

访问上传的文件详细信息相当简单,以下是如何获取与旧样式文件上传数组中提供的数据相同的数据

$name = $attachment->getClientFilename();
$type = $attachment->getClientMediaType();
$size = $attachment->getSize();
$tmpName = $attachment->getStream()->getMetadata('uri');
$error = $attachment->getError();

将上传的文件从其临时位置移动到所需的目标位置,不需要手动访问临时文件,而是可以通过使用对象的 moveTo() 方法轻松完成

$attachment->moveTo($targetPath);

在 HTTP 环境中,moveTo() 方法会自动验证文件是否为实际上传的文件,并在必要时抛出异常。在 CLI 环境中,不存在上传文件的概念,它将允许您移动引用的文件,无论其来源如何,这使得测试文件上传成为可能。

Cake\Http\ServerRequest::getUploadedFile($path)

返回特定路径下的上传文件。路径使用与 Cake\Http\ServerRequest::getData() 方法相同的点语法

$attachment = $this->request->getUploadedFile('attachment');

Cake\Http\ServerRequest::getData() 不同,Cake\Http\ServerRequest::getUploadedFile() 仅在给定路径存在实际文件上传时才会返回数据,如果给定路径存在常规的非文件请求主体数据,则此方法将返回 null,就像它对任何不存在的路径一样。

Cake\Http\ServerRequest::getUploadedFiles()

返回所有上传的文件,以规范化的数组结构存储。对于上面的示例,文件输入名称为 attachment,结构将如下所示

[
      'attachment' => object(Laminas\Diactoros\UploadedFile) {
          // ...
      }
]
Cake\Http\ServerRequest::withUploadedFiles(array $files)

此方法设置请求对象的上传文件,它接受一个包含实现 \Psr\Http\Message\UploadedFileInterface 的对象的数组。它将替换所有可能已存在的上传文件

$files = [
    'MyModel' => [
        'attachment' => new \Laminas\Diactoros\UploadedFile(
            $streamOrFile,
            $size,
            $errorStatus,
            $clientFilename,
            $clientMediaType
        ),
        'anotherAttachment' => new \Laminas\Diactoros\UploadedFile(
            '/tmp/hfz6dbn.tmp',
            123,
            \UPLOAD_ERR_OK,
            'attachment.txt',
            'text/plain'
        ),
    ],
];

$this->request = $this->request->withUploadedFiles($files);

注意

通过此方法添加到请求中的上传文件将不会在请求主体数据中可用,即您无法通过 Cake\Http\ServerRequest::getData() 检索它们!如果您需要将它们也放在请求数据中,则必须通过 Cake\Http\ServerRequest::withData()Cake\Http\ServerRequest::withParsedBody() 设置它们。

PUT、PATCH 或 DELETE 数据

Cake\Http\ServerRequest::getBody()

在构建 REST 服务时,您经常在 PUTDELETE 请求上接受请求数据。任何 application/x-www-form-urlencoded 请求主体数据将自动解析,并通过 $request->getData()PUTDELETE 请求中提供。如果您正在接受 JSON 或 XML 数据,则可以使用 getBody() 访问原始数据

// Get the stream wrapper on the request body
$body = $request->getBody();

// Get the request body as a string
$bodyString = (string)$request->getBody();

如果您的请求包含 XML 或 JSON 请求内容,您应该考虑使用 Body Parser 中间件 让 CakePHP 自动解析这些内容类型,从而使解析后的数据在 $request->getData()$request->getParsedBody() 中可用。

环境变量(来自 $_SERVER 和 $_ENV)

Cake\Http\ServerRequest::putenv($key, $value = null)

ServerRequest::getEnv()getenv() 全局函数的包装器,充当环境变量的 getter/setter,无需修改全局 $_SERVER$_ENV

// Get the host
$host = $this->request->getEnv('HTTP_HOST');

// Set a value, generally helpful in testing.
$this->request->withEnv('REQUEST_METHOD', 'POST');

要访问请求中的所有环境变量,请使用 getServerParams()

$env = $this->request->getServerParams();

XML 或 JSON 数据

使用 REST 的应用程序通常会在非 URL 编码的帖子主体中交换数据。您可以使用 input() 以任何格式读取输入数据。通过提供解码函数,您可以在反序列化格式中接收内容

// Get JSON encoded data submitted to a PUT/POST action
$jsonData = $this->request->input('json_decode');

某些反序列化方法在调用时需要额外的参数,例如 json_decode 上的“as array”参数。如果您希望将 XML 转换为 DOMDocument 对象,input() 也支持传入额外的参数

// Get XML encoded data submitted to a PUT/POST action
$data = $this->request->input('Cake\Utility\Xml::build', ['return' => 'domdocument']);

路径信息

请求对象还提供有关应用程序中路径的有用信息。 basewebroot 属性对于生成 URL 和确定应用程序是否在子目录中很有用。您可以使用的属性是

// Assume the current request URL is /subdir/articles/edit/1?page=1

// Holds /subdir/articles/edit/1?page=1
$here = $request->getRequestTarget();

// Holds /subdir
$base = $request->getAttribute('base');

// Holds /subdir/
$base = $request->getAttribute('webroot');

检查请求条件

Cake\Http\ServerRequest::is($type, $args...)

请求对象提供了一种检查给定请求中某些条件的方法。通过使用 is() 方法,您可以检查许多常见条件,以及检查其他特定于应用程序的请求条件

$isPost = $this->request->is('post');

您还可以通过使用 Cake\Http\ServerRequest::addDetector() 来扩展可用的请求检测器,以创建新的检测器类型。您可以创建不同类型的检测器

  • 环境值比较 - 将从 env() 获取的值与提供的值进行比较。

  • 标头值比较 - 如果指定的标头存在且具有指定的值,或者如果可调用函数返回 true。

  • 模式值比较 - 模式值比较允许您将从 env() 获取的值与正则表达式进行比较。

  • 基于选项的比较 - 基于选项的比较使用选项列表创建正则表达式。随后调用添加已定义的选项检测器将合并选项。

  • 回调检测器 - 回调检测器允许您提供“回调”类型来处理检查。回调将以请求对象作为其唯一参数接收。

Cake\Http\ServerRequest::addDetector($name, $options)

一些示例将是

// Add an environment detector.
$this->request->addDetector(
    'post',
    ['env' => 'REQUEST_METHOD', 'value' => 'POST']
);

// Add a pattern value detector.
$this->request->addDetector(
    'iphone',
    ['env' => 'HTTP_USER_AGENT', 'pattern' => '/iPhone/i']
);

// Add an option detector
$this->request->addDetector('internalIp', [
    'env' => 'CLIENT_IP',
    'options' => ['192.168.0.101', '192.168.0.100']
]);


// Add a header detector with value comparison
$this->request->addDetector('fancy', [
    'env' => 'CLIENT_IP',
    'header' => ['X-Fancy' => 1]
]);

// Add a header detector with callable comparison
$this->request->addDetector('fancy', [
    'env' => 'CLIENT_IP',
    'header' => ['X-Fancy' => function ($value, $header) {
        return in_array($value, ['1', '0', 'yes', 'no'], true);
    }]
]);

// Add a callback detector. Must be a valid callable.
$this->request->addDetector(
    'awesome',
    function ($request) {
        return $request->getParam('awesome');
    }
);

// Add a detector that uses additional arguments.
$this->request->addDetector(
    'csv',
    [
        'accept' => ['text/csv'],
        'param' => '_ext',
        'value' => 'csv',
    ]
);

有几个内置的检测器可以使用

  • is('get') 检查当前请求是否为 GET。

  • is('put') 检查当前请求是否为 PUT。

  • is('patch') 检查当前请求是否为 PATCH。

  • is('post') 检查当前请求是否为 POST。

  • is('delete') 检查当前请求是否为 DELETE。

  • is('head') 检查当前请求是否为 HEAD。

  • is('options') 检查当前请求是否为 OPTIONS。

  • is('ajax') 检查当前请求是否附带 X-Requested-With = XMLHttpRequest。

  • is('ssl') 检查请求是否通过 SSL。

  • is('flash') 检查请求是否具有 Flash 的 User-Agent。

  • is('json') 检查请求 URL 是否具有“json”扩展名或 Accept 标头是否设置为“application/json”。

  • is('xml') 检查请求 URL 是否具有“xml”扩展名或 Accept 标头是否设置为“application/xml”或“text/xml”。

ServerRequest 还包括诸如 Cake\Http\ServerRequest::domain()Cake\Http\ServerRequest::subdomains()Cake\Http\ServerRequest::host() 之类的方法,使使用子域的应用程序更简单。

会话数据

要访问给定请求的会话,请使用 getSession() 方法或使用 session 属性

$session = $this->request->getSession();
$session = $this->request->getAttribute('session');

$data = $session->read('sessionKey');

有关更多信息,请参阅 会话 文档,了解如何使用会话对象。

主机和域名

Cake\Http\ServerRequest::domain($tldLength = 1)

返回您的应用程序运行所在的域名

// Prints 'example.org'
echo $request->domain();
Cake\Http\ServerRequest::subdomains($tldLength = 1)

返回应用程序运行所在的子域,以数组形式

// Returns ['my', 'dev'] for 'my.dev.example.org'
$subdomains = $request->subdomains();
Cake\Http\ServerRequest::host()

返回应用程序所在的 host

// Prints 'my.dev.example.org'
echo $request->host();

读取 HTTP 方法

Cake\Http\ServerRequest::getMethod()

返回请求使用的方法

// Output POST
echo $request->getMethod();

限制操作接受的 HTTP 方法

Cake\Http\ServerRequest::allowMethod($methods)

设置允许的 HTTP 方法。 如果不匹配,将抛出 MethodNotAllowedException。 405 响应将包含必需的 Allow 标头以及传递的方法

public function delete()
{
    // Only accept POST and DELETE requests
    $this->request->allowMethod(['post', 'delete']);
    ...
}

读取 HTTP 头

允许您访问用于请求的任何 HTTP_* 标头。 例如

// Get the header as a string
$userAgent = $this->request->getHeaderLine('User-Agent');

// Get an array of all values.
$acceptHeader = $this->request->getHeader('Accept');

// Check if a header exists
$hasAcceptHeader = $this->request->hasHeader('Accept');

虽然某些 apache 安装不会使 Authorization 标头可用,但 CakePHP 会根据需要通过 apache 特定方法提供它。

Cake\Http\ServerRequest::referer($local = true)

返回请求的引用地址。

Cake\Http\ServerRequest::clientIp()

返回当前访问者的 IP 地址。

信任代理头

如果您的应用程序位于负载均衡器后面或运行在云服务上,您通常会在您的请求中获取负载均衡器主机、端口和方案。 负载均衡器通常还会发送带有原始值的 HTTP-X-Forwarded-* 标头。 CakePHP 默认情况下不会使用转发标头。 要使请求对象使用这些标头,请将 trustProxy 属性设置为 true

$this->request->trustProxy = true;

// These methods will now use the proxied headers.
$port = $this->request->port();
$host = $this->request->host();
$scheme = $this->request->scheme();
$clientIp = $this->request->clientIp();

一旦信任了代理,clientIp() 方法将使用 X-Forwarded-For 标头中的最后一个 IP 地址。 如果您的应用程序位于多个代理后面,您可以使用 setTrustedProxies() 来定义您控制的代理的 IP 地址

$request->setTrustedProxies(['127.1.1.1', '127.8.1.3']);

在信任代理后,clientIp() 将使用 X-Forwarded-For 标头中的第一个 IP 地址,前提是它是不来自受信任代理的唯一值。

检查 Accept 头

Cake\Http\ServerRequest::accepts($type = null)

找出客户端接受的哪些内容类型,或检查它是否接受特定类型的内容。

获取所有类型

$accepts = $this->request->accepts();

检查单个类型

$acceptsJson = $this->request->accepts('application/json');
Cake\Http\ServerRequest::acceptLanguage($language = null)

获取客户端接受的所有语言,或检查是否接受特定语言。

获取接受的语言列表

$acceptsLanguages = $this->request->acceptLanguage();

检查是否接受特定语言

$acceptsSpanish = $this->request->acceptLanguage('es-es');

读取 Cookie

可以通过多种方法读取请求 Cookie

// Get the cookie value, or null if the cookie is missing.
$rememberMe = $this->request->getCookie('remember_me');

// Read the value, or get the default of 0
$rememberMe = $this->request->getCookie('remember_me', 0);

// Get all cookies as an hash
$cookies = $this->request->getCookieParams();

// Get a CookieCollection instance
$cookies = $this->request->getCookieCollection()

有关如何处理 Cookie 集合,请参阅 Cake\Http\Cookie\CookieCollection 文档。

上传的文件

请求在 getData()getUploadedFiles() 中公开上传的文件数据,作为 UploadedFileInterface 对象

// Get a list of UploadedFile objects
$files = $request->getUploadedFiles();

// Read the file data.
$files[0]->getStream();
$files[0]->getSize();
$files[0]->getClientFileName();

// Move the file.
$files[0]->moveTo($targetPath);

操作 URI

请求包含一个 URI 对象,该对象包含用于与请求的 URI 交互的方法

// Get the URI
$uri = $request->getUri();

// Read data out of the URI.
$path = $uri->getPath();
$query = $uri->getQuery();
$host = $uri->getHost();

响应

class Cake\Http\Response

Cake\Http\Response 是 CakePHP 中的默认响应类。 它封装了许多用于在应用程序中生成 HTTP 响应的特性和功能。 它还有助于测试,因为它可以被模拟/存根,允许您检查将要发送的标头。

Response 提供了一个接口来包装常见的响应相关任务,例如

  • 发送重定向标头。

  • 发送内容类型标头。

  • 发送任何标头。

  • 发送响应主体。

处理内容类型

Cake\Http\Response::withType($contentType = null)

您可以使用 Cake\Http\Response::withType() 控制应用程序响应的 Content-Type。 如果您的应用程序需要处理 Response 中未内置的内容类型,您也可以使用 setTypeMap() 对它们进行映射

// Add a vCard type
$this->response->setTypeMap('vcf', ['text/v-card']);

// Set the response Content-Type to vcard.
$this->response = $this->response->withType('vcf');

通常,您希望在控制器的 beforeFilter() 回调中映射其他内容类型,以便您可以从 内容类型协商 提供的自动视图切换中获益。

发送文件

Cake\Http\Response::withFile(string $path, array $options = [])

有时您希望将文件作为请求的响应发送。 您可以通过使用 Cake\Http\Response::withFile() 来实现这一点

public function sendFile($id)
{
    $file = $this->Attachments->getFile($id);
    $response = $this->response->withFile($file['path']);
    // Return the response to prevent controller from trying to render
    // a view.
    return $response;
}

如上例所示,您必须将文件路径传递给该方法。 如果它是 Cake\Http\Response::$_mimeTypes 中列出的已知文件类型,CakePHP 将发送适当的内容类型标头。 您可以通过使用 Cake\Http\Response::withType() 方法在调用 Cake\Http\Response::withFile() 之前添加新类型。

如果需要,您还可以通过指定选项来强制将文件下载而不是在浏览器中显示

$response = $this->response->withFile(
    $file['path'],
    ['download' => true, 'name' => 'foo']
);

支持的选项是

name

名称允许您指定要发送给用户的备用文件名。

download

一个布尔值,指示是否应设置标头以强制下载。

将字符串作为文件发送

您可以使用磁盘上不存在的文件进行响应,例如从字符串动态生成的 pdf 或 ics

public function sendIcs()
{
    $icsString = $this->Calendars->generateIcs();
    $response = $this->response;

    // Inject string content into response body
    $response = $response->withStringBody($icsString);

    $response = $response->withType('ics');

    // Optionally force file download
    $response = $response->withDownload('filename_for_download.ics');

    // Return response object to prevent controller from trying to render
    // a view.
    return $response;
}

设置标头

Cake\Http\Response::withHeader($header, $value)

标头的设置是通过 Cake\Http\Response::withHeader() 方法完成的。 与所有 PSR-7 接口方法一样,此方法返回带有新标头的实例

// Add/replace a header
$response = $response->withHeader('X-Extra', 'My header');

// Set multiple headers
$response = $response->withHeader('X-Extra', 'My header')
    ->withHeader('Location', 'http://example.com');

// Append a value to an existing header
$response = $response->withAddedHeader('Set-Cookie', 'remember_me=1');

设置标头时不会发送标头。 相反,它们会保留到响应由 Cake\Http\Server 发出为止。

您现在可以使用便捷方法 Cake\Http\Response::withLocation() 直接设置或获取重定向位置标头。

设置主体

Cake\Http\Response::withStringBody($string)

要将字符串设置为响应主体,请执行以下操作

// Set a string into the body
$response = $response->withStringBody('My Body');

// If you want a json response
$response = $response->withType('application/json')
    ->withStringBody(json_encode(['Foo' => 'bar']));
Cake\Http\Response::withBody($body)

要设置响应主体,请使用 withBody() 方法,该方法由 Laminas\Diactoros\MessageTrait 提供

$response = $response->withBody($stream);

确保 $stream 是一个 Psr\Http\Message\StreamInterface 对象。 有关如何创建新流的信息,请参见下文。

您还可以使用 Laminas\Diactoros\Stream 流从文件流式传输响应

// To stream from a file
use Laminas\Diactoros\Stream;

$stream = new Stream('/path/to/file', 'rb');
$response = $response->withBody($stream);

您还可以使用 CallbackStream 从回调流式传输响应。 当您需要将图像、CSV 文件或 PDF 等资源流式传输到客户端时,这很有用

// Streaming from a callback
use Cake\Http\CallbackStream;

// Create an image.
$img = imagecreate(100, 100);
// ...

$stream = new CallbackStream(function () use ($img) {
    imagepng($img);
});
$response = $response->withBody($stream);

设置字符集

Cake\Http\Response::withCharset($charset)

设置响应中将使用的字符集

$this->response = $this->response->withCharset('UTF-8');

与浏览器缓存交互

Cake\Http\Response::withDisabledCache()

有时需要强制浏览器不缓存控制器操作的结果。 Cake\Http\Response::withDisabledCache() 就是为此而设计的。

public function index()
{
    // Disable caching
    $this->response = $this->response->withDisabledCache();
}

警告

在尝试将文件发送到 Internet Explorer 时,从 SSL 域禁用缓存会导致错误。

Cake\Http\Response::withCache($since, $time = '+1 day')

您也可以告诉客户端您希望他们缓存响应。 使用 Cake\Http\Response::withCache()

public function index()
{
    // Enable caching
    $this->response = $this->response->withCache('-1 minute', '+5 days');
}

以上会告诉客户端将生成的响应缓存 5 天,希望加快访问者的体验。 withCache() 方法将 Last-Modified 值设置为第一个参数。 Expires 标头和 max-age 指令根据第二个参数设置。Cache-Control 的 public 指令也已设置。

微调 HTTP 缓存

加速应用程序的最佳和最简单方法之一是使用 HTTP 缓存。 在此缓存模型下,您只需要通过设置一些标头(例如修改时间和响应实体标签)来帮助客户端决定是否应该使用缓存的响应副本。

HTTP 使用两种模型(过期和验证)来替代强制您为缓存编写逻辑并为数据更改后使缓存失效(刷新),这通常更容易使用。

除了使用 Cake\Http\Response::withCache() 之外,您还可以使用许多其他方法来微调 HTTP 缓存标头以利用浏览器或反向代理缓存。

缓存控制标头

Cake\Http\Response::withSharable($public, $time = null)

在过期模型下使用,此标头包含多个指示器,可以更改浏览器或代理使用缓存内容的方式。 Cache-Control 标头可能如下所示

Cache-Control: private, max-age=3600, must-revalidate

Response 类帮助您使用一些实用方法来设置此标头,这些方法将生成最终有效的 Cache-Control 标头。 第一个是 withSharable() 方法,它指示响应是否应被视为对不同用户或客户端共享。 此方法实际上控制了此标头的 publicprivate 部分。 将响应设置为私有表示其全部或部分内容是为单个用户准备的。 为了利用共享缓存,控制指令必须设置为公用。

此方法的第二个参数用于为缓存指定 max-age,它是在响应不再被认为新鲜后的秒数。

public function view()
{
    // ...
    // Set the Cache-Control as public for 3600 seconds
    $this->response = $this->response->withSharable(true, 3600);
}

public function my_data()
{
    // ...
    // Set the Cache-Control as private for 3600 seconds
    $this->response = $this->response->withSharable(false, 3600);
}

Response 公开了用于设置 Cache-Control 标头中每个指令的单独方法。

过期标头

Cake\Http\Response::withExpires($time)

您可以将 Expires 标头设置为一个日期和时间,在此之后,响应不再被认为是新鲜的。 可以使用 withExpires() 方法设置此标头。

public function view()
{
    $this->response = $this->response->withExpires('+5 days');
}

此方法还接受 DateTime 实例或任何可以由 DateTime 类解析的字符串。

Etag 标头

Cake\Http\Response::withEtag($tag, $weak = false)

HTTP 中的缓存验证通常用于内容不断变化的情况,并要求应用程序仅在缓存不再新鲜时才生成响应内容。 在此模型下,客户端继续将页面存储在缓存中,但它每次都会询问应用程序资源是否已更改,而不是直接使用它。 这通常用于静态资源,例如图像和其他资产。

withEtag() 方法(称为实体标签)是一个字符串,它唯一地标识了请求的资源,就像校验和为文件那样,以便确定它是否与缓存的资源匹配。

为了利用此标头,您必须手动调用 isNotModified() 方法,或者在控制器中包含 检查 HTTP 缓存

public function index()
{
    $articles = $this->Articles->find('all')->all();

    // Simple checksum of the article contents.
    // You should use a more efficient implementation
    // in a real world application.
    $checksum = md5(json_encode($articles));

    $response = $this->response->withEtag($checksum);
    if ($response->isNotModified($this->request)) {
        return $response;
    }

    $this->response = $response;
    // ...
}

注意

出于性能和兼容性方面的考虑,大多数代理用户可能应该考虑使用 Last Modified 标头而不是 Etag。

Last Modified 标头

Cake\Http\Response::withModified($time)

同样,在 HTTP 缓存验证模型下,您可以设置 Last-Modified 标头以指示资源上次修改的日期和时间。 设置此标头有助于 CakePHP 告诉缓存客户端响应是否已修改,具体取决于它们的缓存。

为了利用此标头,您必须手动调用 isNotModified() 方法,或者在控制器中包含 检查 HTTP 缓存

public function view()
{
    $article = $this->Articles->find()->first();
    $response = $this->response->withModified($article->modified);
    if ($response->isNotModified($this->request)) {
        return $response;
    }
    $this->response;
    // ...
}

Vary 标头

Cake\Http\Response::withVary($header)

在某些情况下,您可能希望使用相同的 URL 提供不同的内容。 如果您有一个多语言页面或根据浏览器提供不同的 HTML,这通常是这种情况。 在这种情况下,您可以使用 Vary 标头。

$response = $this->response->withVary('User-Agent');
$response = $this->response->withVary('Accept-Encoding', 'User-Agent');
$response = $this->response->withVary('Accept-Language');

发送未修改的响应

Cake\Http\Response::isNotModified(Request $request)

将请求对象的缓存标头与响应的缓存标头进行比较,并确定它是否仍然可以被认为是新鲜的。 如果是,则删除响应内容,并发送 304 未修改 标头。

// In a controller action.
if ($this->response->isNotModified($this->request)) {
    return $this->response;
}

设置 Cookie

可以使用数组或 Cake\Http\Cookie\Cookie 对象将 Cookie 添加到响应中。

use Cake\Http\Cookie\Cookie;
use DateTime;

// Add a cookie
$this->response = $this->response->withCookie(Cookie::create(
    'remember_me',
    'yes',
    // All keys are optional
    [
        'expires' => new DateTime('+1 year'),
        'path' => '',
        'domain' => '',
        'secure' => false,
        'httponly' => false,
        'samesite' => null // Or one of CookieInterface::SAMESITE_* constants
    ]
));

有关如何使用 Cookie 对象,请参阅 创建 Cookie 部分。 您可以使用 withExpiredCookie() 在响应中发送一个过期的 Cookie。 这将使浏览器删除其本地 Cookie。

$this->response = $this->response->withExpiredCookie(new Cookie('remember_me'));

设置跨域请求标头 (CORS)

cors() 方法用于使用流畅的接口定义与 HTTP 跨域资源共享 相关的标头。

$this->response = $this->response->cors($this->request)
    ->allowOrigin(['*.cakephp.org'])
    ->allowMethods(['GET', 'POST'])
    ->allowHeaders(['X-CSRF-Token'])
    ->allowCredentials()
    ->exposeHeaders(['Link'])
    ->maxAge(300)
    ->build();

CORS 相关标头仅在满足以下条件时才会应用于响应。

  1. 请求具有 Origin 标头。

  2. 请求的 Origin 值与允许的 Origin 值之一匹配。

提示

CakePHP 没有内置的 CORS 中间件,因为处理 CORS 请求非常依赖于应用程序。 如果需要,我们建议您构建自己的 CORSMiddleware 并根据需要调整响应对象。

在响应已发送后运行逻辑

在基于 fastcgi 的环境中,您可以侦听 Server.terminate 事件以在响应已发送到客户端后运行逻辑。 terminate 事件将传递 requestresponserequest 是从应用程序的 DI 容器中获取的,或者如果 DI 容器没有注册请求,则从 Router::getRequest() 中获取。

警告

在非 FastCGI 环境中,Server.terminate 事件会在响应发送之前触发。

新增于 5.1.0 版本。

不可变响应的常见错误

响应对象提供了一些将响应视为不可变对象的方法。不可变对象有助于防止难以追踪的意外副作用,并减少重构导致的方法调用顺序更改引起的错误。虽然它们提供了许多好处,但不可变对象可能需要一些时间来适应。任何以 with 开头的 方法都以不可变的方式操作响应,并且会 **始终** 返回一个 **新的** 实例。忘记保留修改后的实例是在使用不可变对象时人们最常犯的错误

$this->response->withHeader('X-CakePHP', 'yes!');

在上面的代码中,响应将缺少 X-CakePHP 标头,因为 withHeader() 方法的返回值未被保留。要更正上面的代码,您应该编写

$this->response = $this->response->withHeader('X-CakePHP', 'yes!');