lucinda/headers

封装HTTP请求和响应头部的API,也适用于缓存/跨源资源共享验证

v2.0.2 2022-06-12 10:11 UTC

This package is auto-updated.

Last update: 2024-09-12 15:44:11 UTC


README

目录

关于

此API封装从客户端接收到的HTTP请求头部和要发送的响应头部,提供绑定它们以进行缓存和CORS验证的能力。

diagram

可以通过以下步骤完成此任务

  • 配置:设置一个XML文件,其中配置缓存/CORS验证策略
  • 绑定点:将XML/代码中定义的用户定义组件绑定到API原型,以获得必要的功能
  • 初始化:使用Wrapper将上述XML读取到Policy,将HTTP请求头部读取到Request,然后初始化Response,封装HTTP响应头部逻辑。
  • 验证:使用上述内容执行缓存/CORS验证,并相应地设置Response
  • 显示:使用Response头部编译上述内容(或由用户单独设置)将响应发送给调用者

API完全符合PSR-4规范,仅需要PHP8.1+解释器和SimpleXML扩展。要快速了解其工作原理,请查看

  • 安装:描述如何在计算机上安装API,根据上述步骤
  • 单元测试:API具有100%的单元测试覆盖率,使用UnitTest API代替PHPUnit以获得更大的灵活性
  • 示例:基于单元测试展示了API功能的一个深入示例

所有类都包含在Lucinda\Headers命名空间中!

配置

要配置此API,您必须有一个包含以下标签的XML文件

  • headers:(必需)全局配置API
  • routes:(可选)根据请求的路由配置API,CORS请求验证所需

头部

此标签的最大语法为

<headers no_cache="..." cache_expiration="..." allow_credentials="..." cors_max_age="..." allowed_request_headers="..." allowed_response_headers="..."/>

其中

  • headers:(必需)包含全局头部验证策略
    • no_cache:(可选)禁用网站中所有页面的HTTP缓存(可以是0或1;默认为0),除非在请求匹配的页面中特别激活
    • cache_expiration:(可选)所有页面响应在网站中将缓存多长时间(必须为正数)
    • allow_credentials:(可选)是否允许CORS请求中包含凭据(可以是0或1;默认为0)
    • cors_max_age:(可选)CORS响应将缓存多长时间(必须为正数)
    • allowed_request_headers(可选):使用逗号分隔的非标准请求头列表,您的网站支持。如果没有提供,并且请求了 CORS Access-Control-Request-Headers,则假定那里列出的头为支持的头!
    • allowed_response_headers(可选):使用逗号分隔的响应头列表,用于暴露

示例

<headers no_cache="1" cache_expiration="10" allow_credentials="1" cors_max_age="5" allowed_request_headers="X-Custom-Header, Upgrade-Insecure-Requests" allowed_response_headers="Content-Length, X-Kuma-Revision"/>

路由

此标签的最小语法为

<routes>
    <route id="..." no_cache="..." cache_expiration="..." allowed_methods="..."/>
    ...
</routes>

其中

  • routes(必填):包含网站路由列表,每个路由由一个 route 标签标识
    • route(必填):包含特定路由的策略
      • id(必填):页面相对URL(例如:administration)
      • no_cache(可选):禁用相应路由的HTTP缓存(可以是0或1;默认为0)
      • cache_expiration(可选):相应路由响应在网站上缓存而不重新验证的持续时间(必须为正数)
      • allowed_methods(可选):支持相应路由的HTTP请求方法列表。如果没有提供,并且请求了 CORS Access-Control-Request-Method,则假定该方法为支持的方法!示例
<routes>
    <route id="index" no_cache="0" cache_expiration="10" allowed_methods="GET"/>
    <route id="login" no_cache="1" allowed_methods="GET,POST"/>    
</routes>

绑定点

为了保持灵活性和实现最高性能,API不做任何非必要的假设!相反,它为开发者提供了一种通过 validateCache 方法将程序绑定到其原型的能力,该方法位于 Wrapper

执行

初始化

配置策略后,它们可以使用 Wrapper 绑定到请求和响应,该 Wrapper 创建并处理三个对象

  • Policy:封装从XML中检测到的验证策略
  • Request:封装根据 RFC-7231 规范从客户端接收到的HTTP请求头
  • Response:封装根据 RFC-7231 规范发送回客户端的HTTP响应头

一旦设置,PolicyRequest 就成为不可变的(因为 过去无法改变)。Policy 只会在内部使用,而 Request 只会暴露getter。另一方面,Response 只在设置时创建,而将设置的责任留给开发者。这是因为在没有进行 验证 的情况下,请求和响应头之间没有默认的链接。鉴于上述情况,Wrapper 定义了以下公共方法

显然,开发者需要 了解 从客户端接收到的头,并 设置 发送回响应的头,但它们之间的链接方式取决于您的应用程序。然而,有一些特殊情况,请求和响应头(以及HTTP状态)在逻辑上绑定

缓存验证

缓存验证的目的是根据头信息与客户端浏览器的缓存进行通信,尽可能使您的网站立即显示。通信语言由RFC-7232RFC-7234规范确定,您的网站(通过此API)和浏览器都必须遵守。

缓存验证的工作原理

其工作原理过于复杂,无法在此处详细说明,以下仅涵盖典型用例。HTTP标准允许您根据条件头进行以下简单的通信方法

  • 客户端-服务器:给我您的网站上的X页面
  • 服务器-客户端:这是我对页面X的响应,状态头为200 OK,通过ETag响应头(或由Last-Modified响应头定义的GMT日期)唯一标识
  • 客户端:好的,我已经收到响应并保存到我的缓存中,并将其与ETag(或Last-Modified)相关联。
  • ...(过了一段时间)...
  • 客户端-服务器:再次给我您的网站上的X页面。我也会发送之前收到的匹配的ETag/Last-Modified,作为If-None-Match/If-Modified-Since请求头,以便您检查页面是否已更改!
  • 服务器-客户端:响应保持不变,因此我将只发送304 Not Modified状态头,不包含响应体
  • 客户端:好的,所以我会从我的浏览器缓存中显示页面
  • ...(过了一段时间)...
  • 客户端-服务器:再次给我您的网站上的X页面以及相同的请求头
  • 服务器-客户端:响应已更改,因此这次我将用200 OK状态头、完整的响应体以及新的ETag(或Last-Modified)进行响应
  • 客户端:好的,我已经收到响应并将其保存到我的缓存中,并将其与新的ETag(或Last-Modified)相关联。

上述方法存在一个缺点,因为它假设缓存为过时,因此需要服务器往返检查请求的资源是否已更改(重新验证)。因此,HTTP标准提供了一种使用Cache-Control响应头部的替代最快解决方案。

  • 客户端-服务器:给我您的网站上的X页面
  • 服务器-客户端:这是我针对页面X的响应,并假设它为X秒内的新鲜,由Cache-Control中的max-age指令定义。
  • 客户端:好的,我已经收到响应并将其保存到我的缓存中。在未来对同一页面的请求中,除非X秒已过,否则我不会询问服务器,而是显示缓存中的响应!

上述两种通信方式并非互斥。成熟的应用程序同时使用这两种方法,根据请求的页面采用不同的策略:一些页面可以默认假设为过时,其他页面允许一些新鲜度,而最后一些可能甚至与缓存不兼容,因为输出在每个请求中都会更改。

缓存验证是如何实现的

要设置与缓存相关的响应头,请使用以下Response方法。

要读取与缓存相关的请求头,请使用以下Request方法。

幸运的是,一旦你运行Wrapper对象的validateCache方法,所有这些都会自动完成。此方法

  • 根据同名的请求头和由Policy封装的XML设置配置Cache-Control响应头。
  • 根据Cacheable表示的请求资源设置ETag响应头,如果存在的话。
  • 根据Cacheable表示的请求资源设置Last-Modified响应头,如果存在的话。
  • 读取与缓存相关的请求头,将它们与Cacheable表示进行匹配,并返回根据RFC规范返回的HTTP状态码

从开发者的角度来看,你只需要

  • (可选)根据配置阶段设置no_cachecache_expiration XML标签属性。
  • (必填)实现请求资源的Cacheable表示(它如何转换为ETag或最后修改时间)。
  • (必填)在渲染响应时使用validateCache返回的HTTP状态码。

返回的可能HTTP状态码包括:

  • 200:这意味着响应是正常的,并且必须带有主体。
  • 304:这意味着响应未修改,并且必须不带主体。
  • 412:这意味着一个条件头失败,因此应用程序应退出并显示错误。

跨源资源共享验证

CORS预请求在客户端浏览器自动触发,当它遇到由于安全原因需要preflight的情况时。

CORS验证是如何工作的

触发预检请求的因素超出了本API文档的范围。如果您想了解更多信息,请查看

  • 简单请求,了解如何避免预检请求
  • 预检请求,了解什么触发了预检请求以及接下来会发生什么

如何实现CORS验证

要设置与CORS相关的响应头,请使用以下Response方法

要读取与CORS相关的请求头,请使用以下Request方法

幸运的是,一旦您运行了Wrapper对象的validateCORS方法,所有这些都会由API自动完成。此方法

  • 要求开发者将源主机名(例如:https://www.google.com)作为参数。由于它可能因开发环境的不同而不同,因此不能在XML中设置!如果没有提供,则认为任何Origin都是有效的!
  • 根据Policy中封装的allow_credentialscors_max_ageallowed_request_headersallowed_response_headers XML属性以及接收到的CORS请求头设置CORS响应头

显示

一旦响应头、状态和体(如果有)可用,您就可以最终将头信息发送回客户端。示例

$wrapper = new Lucinda\Headers\Wrapper("configuration.xml", $_SERVER["REQUEST_URI"], getallheaders());
// developer now reads request headers, sets response headers, compiles $responseBody then applies cache validation:
$httpStatus = $wrapper->validateCache(new MyCacheable($responseBody), $_SERVER["REQUEST_METHOD"]);
// now response is ready for display
http_response_code(httpStatus);
$headers = $wrapper->getResponse()->toArray();
foreach ($headers as $name=>$value) {
    header($name.": ".$value);
}
if ($httpStatus!=304) {
    echo $responseBody;
}

安装

首先选择一个文件夹,将其关联到一个域名,然后在控制台输入以下命令

composer require lucinda/headers

然后创建一个包含配置设置的configuration.xml文件(见上文的配置),并在项目根目录中创建一个index.php文件(见displayinitialization),其代码如下

$wrapper = new Lucinda\Headers\Wrapper("configuration.xml", $_SERVER["REQUEST_URI"], getallheaders());
// if request is CORS, response can be done immediately
if ($_SERVER["REQUEST_METHOD"]=="OPTIONS") {
    $wrapper->validateCORS((!empty($_SERVER['HTTPS'])?"https":"http")."://".$_SERVER["SERVER_NAME"]);
    $headers = $wrapper->getResponse()->toArray();
    foreach ($headers as $name=>$value) {
        header($name.": ".$value);
    }
    exit();
}
// developer reads request headers, sets response headers, compiles $responseBody
// developer creates a Cacheable instance (MyCacheable), able to convert $responseBody into an ETag string, then performs cache validation
$httpStatus = $wrapper->validateCache(new MyCacheable($responseBody), $_SERVER["REQUEST_METHOD"]);
// now response is ready for display
http_response_code(httpStatus);
$headers = $wrapper->getResponse()->toArray();
foreach ($headers as $name=>$value) {
    header($name.": ".$value);
}
if ($httpStatus!=304) {
    echo $responseBody;
}

然后确保域名对整个互联网可用,并且所有指向它的请求都重定向到index.php

RewriteEngine on
RewriteRule ^(.*)$ index.php

单元测试

有关测试和示例,请检查API源中的以下文件/文件夹

  • test.php:在控制台中运行单元测试
  • unit-tests.xml:设置单元测试并模拟“loggers”标签
  • tests:来自src文件夹的类的单元测试

示例

要了解如何通过Request解析请求头,请查看其匹配的UnitTest。要了解如何通过Response设置响应头,请查看其匹配的UnitTest。要了解每个头部的详细示例并深入了解它们,没有比Mozilla提供的文档更好的了!

参考指南

Request类

Request类封装了从客户端接收到的HTTP请求头。内部每个方法(除__construct外)都对应一个头部

以下限制适用

  • 不支持多个ETags

Response类

Response 封装了要发送回的 HTTP 响应头。内部每个方法(除 toArray 外)对应一个头

以下限制适用

  • 不支持多个ETags

接口 Cacheable

Cacheable 通过方法定义了缓存验证的蓝图

使用示例

https://github.com/aherne/lucinda-framework-engine/blob/master/src/AbstractCacheable.php https://github.com/aherne/lucinda-framework/blob/master/src/Cacheables/Etag.php