lucinda / headers
封装HTTP请求和响应头部的API,也适用于缓存/跨源资源共享验证
Requires
- php: ^8.1
- ext-simplexml: *
Requires (Dev)
- lucinda/unit-testing: ^2.0
README
目录
关于
此API封装从客户端接收到的HTTP请求头部和要发送的响应头部,提供绑定它们以进行缓存和CORS验证的能力。
可以通过以下步骤完成此任务
- 配置:设置一个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 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,则假定该方法为支持的方法!示例
- route(必填):包含特定路由的策略
<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响应头
一旦设置,Policy 和 Request 就成为不可变的(因为 过去无法改变)。Policy 只会在内部使用,而 Request 只会暴露getter。另一方面,Response 只在设置时创建,而将设置的责任留给开发者。这是因为在没有进行 验证 的情况下,请求和响应头之间没有默认的链接。鉴于上述情况,Wrapper 定义了以下公共方法
显然,开发者需要 了解 从客户端接收到的头,并 设置 发送回响应的头,但它们之间的链接方式取决于您的应用程序。然而,有一些特殊情况,请求和响应头(以及HTTP状态)在逻辑上绑定
- 缓存验证:根据 Policy 验证 Request 头,以与客户端浏览器缓存 通信 并根据要求设置 Response 头
- CORS验证:根据请求头中的策略(见Policy)进行验证,以便回答CORS请求并设置符合CORS协议规范的响应头
缓存验证
缓存验证的目的是根据头信息与客户端浏览器的缓存进行通信,尽可能使您的网站立即显示。通信语言由RFC-7232和RFC-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_cache和cache_expiration XML标签属性。
- (必填)实现请求资源的Cacheable表示(它如何转换为ETag或最后修改时间)。
- (必填)在渲染响应时使用validateCache返回的HTTP状态码。
返回的可能HTTP状态码包括:
跨源资源共享验证
CORS预请求在客户端浏览器自动触发,当它遇到由于安全原因需要preflight的情况时。
CORS验证是如何工作的
触发预检请求的因素超出了本API文档的范围。如果您想了解更多信息,请查看
如何实现CORS验证
要设置与CORS相关的响应头,请使用以下Response方法
要读取与CORS相关的请求头,请使用以下Request方法
幸运的是,一旦您运行了Wrapper对象的validateCORS方法,所有这些都会由API自动完成。此方法
- 要求开发者将源主机名(例如:https://www.google.com)作为参数。由于它可能因开发环境的不同而不同,因此不能在XML中设置!如果没有提供,则认为任何Origin都是有效的!
- 根据Policy中封装的allow_credentials、cors_max_age、allowed_request_headers、allowed_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文件(见display和initialization),其代码如下
$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