- 后端
- 应用安全
- 图像处理
【免费下载链接】captcha
行为验证码(滑动拼图、点选文字),前后端(java)交互,包含h5/Android/IOS/flutter/uni-app的源码和实现
导读
本文围绕当前仓库service/php/vendor/guzzlehttp/psr7/README.md这一文档,系统讲解 Guzzle PSR-7 消息实现的核心能力:从十余种流实现与装饰器、Message 静态 API、Query 查询串解析,到 Uri 的规范化与引用解析。该包以 MIT 协议提供完整的 PSR-7 HTTP 消息实现(版本 2.1.0),并作为 AJ-Captcha PHP 行为验证码后端fastknife/ajcaptcha(service/php/composer.json)的间接依赖随composer install一同安装。读完本文,你将掌握每个流类的适用场景、静态工具方法的使用范式、2.0 从函数 API 到静态 API 的迁移方法,以及 URI 规范化与相对引用解析的 RFC 3986 实践。
一、背景:为什么 AJ-Captcha 会携带 PSR-7 实现
AJ-Captcha PHP 包本身不直接调用 GuzzleHttp\Psr7 命名空间下的类,它主要通过 GD 扩展与 Intervention Image 完成滑动拼图与点选文字的图像生成。但 Intervention Image 2.x 在其依赖中声明了guzzlehttp/psr7(见 service/php/vendor/intervention/image/composer.json 的"guzzlehttp/psr7": "~1.1 || ^2.0"),因此composer install后会在service/php/vendor/guzzlehttp/psr7下落地该实现(本仓库锁定版本为 2.1.0,可从 service/php/vendor/composer/installed.json 确认)。
了解这一点有助于你在排查验证码服务端问题时快速定位:凡是涉及 HTTP 消息对象化、流式读取与 URI 规范化操作的报错栈,都可能来自这个底层库。下面按文档原骨架(Stream 实现 → 静态 API → URI 方法)逐一深入。
二、Stream 实现与装饰器:十一类流的定位与用法
GuzzleHttp\Psr7命名空间下提供了一批实现了Psr\Http\Message\StreamInterface的流类,源码均位于 service/php/vendor/guzzlehttp/psr7/src。以下代码示例均出自原文档,可直接复制运行。
2.1 AppendStream:顺序拼接多段流
GuzzleHttp\Psr7\AppendStream依次读取多个流,把多段内容拼接成一个可整体读出的流:
use GuzzleHttp\Psr7; $a = Psr7\Utils::streamFor('abc, '); $b = Psr7\Utils::streamFor('123.'); $composed = new Psr7\AppendStream([$a, $b]); $composed->addStream(Psr7\Utils::streamFor(' Above all listen to me')); echo $composed; // abc, 123. Above all listen to me.2.2 BufferStream:带高水位标记(hwm)的内存缓冲
GuzzleHttp\Psr7\BufferStream提供可写可读的缓冲流,并对外暴露hwm元数据:当缓冲内容超过配置的高水位时,write()开始返回false,提示上游写入方应放慢速度:
use GuzzleHttp\Psr7; // 缓冲超过 1024 字节后,写入开始返回 false $buffer = new Psr7\BufferStream(1024);2.3 CachingStream:为不可 seek 的流提供回退能力
GuzzleHttp\Psr7\CachingStream把已读过的字节缓存到 PHP 临时流(先内存后落盘),从而允许对不可 seek 的实体流执行seek()——典型场景是重定向后需要回绕请求体:
use GuzzleHttp\Psr7; $original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r')); $stream = new Psr7\CachingStream($original); $stream->read(1024); echo $stream->tell(); // 1024 $stream->seek(0); echo $stream->tell(); // 02.4 DroppingStream:超出容量即丢弃数据
GuzzleHttp\Psr7\DroppingStream是一个装饰器:当底层流已满到阈值时,后续写入的数据被直接丢弃:
use GuzzleHttp\Psr7; $stream = Psr7\Utils::streamFor(); // 空流 // 流超过 10 字节后开始丢弃写入 $dropping = new Psr7\DroppingStream($stream, 10); $dropping->write('01234567890123456789'); echo $stream; // 01234567892.5 FnStream:以函数表组合流行为
GuzzleHttp\Psr7\FnStream允许用一组回调函数组合出一个流,便于测试和轻量扩展:
use GuzzleHttp\Psr7; $stream = Psr7\Utils::streamFor('hi'); $fnStream = Psr7\FnStream::decorate($stream, [ 'rewind' => function () use ($stream) { echo 'About to rewind - '; $stream->rewind(); echo 'rewound!'; } ]); $fnStream->rewind(); // Outputs: About to rewind - rewound!2.6 InflateStream:透明解压 zlib / gzip
GuzzleHttp\Psr7\InflateStream借助 PHP 的zlib.inflate过滤器,解压 RFC 1950(HTTP deflate)或 RFC 1952(gzip)内容。实现上先把输入流转换为 PHP 流资源、追加过滤器,再包装回 Guzzle 流,对调用方完全透明。
2.7 LazyOpenStream:惰性打开文件
GuzzleHttp\Psr7\LazyOpenStream在构造时不打开文件,只有真正发生 IO 时才打开,适合延迟加载大文件:
use GuzzleHttp\Psr7; $stream = new Psr7\LazyOpenStream('/path/to/file', 'r'); // 此时文件尚未打开…… echo $stream->read(10); // 仅在真正读取时才打开并读取文件2.8 LimitStream:读取流的子区间
GuzzleHttp\Psr7\LimitStream从既有流中切出指定长度、指定起始偏移的子流,可用于将大文件分片传输(如 S3 分片上传):
use GuzzleHttp\Psr7; $original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+')); echo $original->getSize(); // >>> 1048576 // 从字节 2048 开始,仅读取 1024 字节 $stream = new Psr7\LimitStream($original, 1024, 2048); echo $stream->getSize(); // >>> 1024 echo $stream->tell(); // >>> 02.9 MultipartStream / NoSeekStream / PumpStream
GuzzleHttp\Psr7\MultipartStream:读取时产出multipart/form-data格式字节流;GuzzleHttp\Psr7\NoSeekStream:包装流并禁止 seek——isSeekable()返回false,调用seek()后继续read()得到NULL:$original = Psr7\Utils::streamFor('foo'); $noSeek = new Psr7\NoSeekStream($original); var_export($noSeek->isSeekable()); // falseGuzzleHttp\Psr7\PumpStream:只读流,由 PHP callable 供数;每次读取时传入建议字节数,callable 可返回更多或更少字节(多余部分内部缓冲),数据耗尽时必须返回false。
2.10 自定义装饰器:StreamDecoratorTrait
文档专门演示了基于GuzzleHttp\Psr7\StreamDecoratorTrait快速实现装饰器:trait 已把Psr\Http\Message\StreamInterface的所有方法代理到底层流,你只需实现自定义方法。例如在读到 EOF 时触发回调:
use Psr\Http\Message\StreamInterface; use GuzzleHttp\Psr7\StreamDecoratorTrait; class EofCallbackStream implements StreamInterface { use StreamDecoratorTrait; private $callback; public function __construct(StreamInterface $stream, callable $cb) { $this->stream = $stream; $this->callback = $cb; } public function read($length) { $result = $this->stream->read($length); // Invoke the callback when EOF is hit. if ($this->eof()) { call_user_func($this->callback); } return $result; } }使用方式:
use GuzzleHttp\Psr7; $original = Psr7\Utils::streamFor('foo'); $eofStream = new EofCallbackStream($original, function () { echo 'EOF!'; }); $eofStream->read(2); $eofStream->read(1); // echoes "EOF!"StreamDecoratorTrait的代理实现可参见 service/php/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php(含__get惰性创建底层流、__toString异常处理等细节)。
2.11 StreamWrapper:把 PSR-7 流当作 PHP 流资源
若需要把 PSR-7 流交给只接受 PHP 流资源的函数,可用GuzzleHttp\Psr7\StreamWrapper::getResource():
use GuzzleHttp\Psr7\StreamWrapper; $stream = GuzzleHttp\Psr7\Utils::streamFor('hello!'); $resource = StreamWrapper::getResource($stream); echo fread($resource, 6); // outputs hello!三、静态 API:从函数式到面向对象
从 1.7.0 起包提供静态 API,用于规避全局函数在包的多副本间冲突的问题;2.0.0 彻底移除了函数式 API。核心入口类为GuzzleHttp\Psr7\Message、Header、Query、Utils与MimeType。
3.1 Message:消息序列化与解析
| 方法 | 签名 | 作用 |
|---|---|---|
Message::toString | (MessageInterface): string | 返回 HTTP 消息的字符串表示 |
Message::bodySummary | (MessageInterface, int $truncateAt = 120): string\|null | 返回消息体摘要,不可打印时返回null |
Message::rewindBody | (MessageInterface): void | 回绕消息体,失败抛异常;仅当tell()非 0 时才真正回绕 |
Message::parseMessage | (string): array | 把 HTTP 消息解析为含start-line、headers、body三键的数组 |
Message::parseRequestUri | (string $path, array $headers): string | 为请求消息构造 URI |
Message::parseRequest | (string): Request | 请求字符串 → Request 对象 |
Message::parseResponse | (string): Response | 响应字符串 → Response 对象 |
序列化示例:
$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); echo GuzzleHttp\Psr7\Message::toString($request);parseRequest/parseResponse的实现位于 service/php/vendor/guzzlehttp/psr7/src/Message.php,它们借助 Rfc7230 解析起行与头字段,再按“请求/响应”语义构造对应对象。
3.2 Header:解析与归一化
Header::parse(string|array $header): array:把;分隔的头部参数解析为键值对数组,无值的参数注入空字符串键。Header::normalize(string|array $header): array:把可能含逗号合并值的头字段拆分为无逗号合并的数组。
3.3 Query:查询串解析与构建
Query::parse(string $str, int|bool $urlEncoding = true): array:查询串 → 关联数组。同键多值时值为数组;不支持PHP 嵌套数组风格(foo[a]=1&foo[b]=2解析为['foo[a]' => '1', 'foo[b]' => '2'])。Query::build(array $params, int|false $encoding = PHP_QUERY_RFC3986): string:数组 → 查询串,可直接用parse()的返回值回建;与http_build_query()不同,遇到数组键时不改写键名。
实现见 service/php/vendor/guzzlehttp/psr7/src/Query.php。
3.4 Utils:通用工具方法
Utils::caselessRemove(iterable $keys, array $data): array:从数据中按键名大小写不敏感地移除项。Utils::copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void:把源流复制到目标流,最多$maxLen字节。Utils::copyToString(StreamInterface $stream, int $maxLen = -1): string:把流内容读入字符串。Utils::hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string:基于 PHPhash_init对整流计算滚动哈希。Utils::modifyRequest(RequestInterface $request, array $changes): RequestInterface:克隆并修改请求,减少多次克隆成本。$changes支持method、set_headers、remove_headers、body、uri、query、version等键。Utils::readLine(StreamInterface $stream, int $maxLength = null): string:按最大缓冲读取一行。Utils::tryFopen(string $filename, string $mode): resource:安全打开 PHP 流资源,把 fopen 失败时的告警转为异常。Utils::uriFor(string|UriInterface $uri): UriInterface:字符串或 UriInterface → UriInterface。
streamFor:最常用的工厂方法
Utils::streamFor(mixed $resource = '', array $options = []): StreamInterface按输入类型创建流。$options可含metadata(自定义元数据)与size(流大小)。支持的$resource类型:
StreamInterface:原样返回;string:以字符串为内容创建流;resource:包装 PHP 流资源;Iterator:创建只读流,读取时迭代器数据持续填充缓冲;- 含
__toString()的对象:先转字符串再建流; NULL:返回空流;callable:创建只读流,按建议字节数调用该 callable,耗尽时必须返回false。
示例:
$stream = GuzzleHttp\Psr7\Utils::streamFor('foo'); $stream = GuzzleHttp\Psr7\Utils::streamFor(fopen('/path/to/file', 'r')); $generator = function ($bytes) { for ($i = 0; $i < $bytes; $i++) { yield ' '; } } $stream = GuzzleHttp\Psr7\Utils::streamFor($generator(100));3.5 MimeType:文件名与扩展名 → MIME 类型
MimeType::fromFilename(string $filename): string|null:按扩展名推断 MIME 类型。MimeType::fromExtension(string $extension): string|null:扩展名 → MIME 类型映射。
3.6 2.0 迁移对照表(原文档全文继承)
函数式 API 已在 2.0.0 移除,迁移对照如下:
| 原函数 | 替代方法 |
|---|---|
str | Message::toString |
uri_for | Utils::uriFor |
stream_for | Utils::streamFor |
parse_header | Header::parse |
normalize_header | Header::normalize |
modify_request | Utils::modifyRequest |
rewind_body | Message::rewindBody |
try_fopen | Utils::tryFopen |
copy_to_string | Utils::copyToString |
copy_to_stream | Utils::copyToStream |
hash | Utils::hash |
readline | Utils::readLine |
parse_request | Message::parseRequest |
parse_response | Message::parseResponse |
parse_query | Query::parse |
build_query | Query::build |
mimetype_from_filename | MimeType::fromFilename |
mimetype_from_extension | MimeType::fromExtension |
_parse_message | Message::parseMessage |
_parse_request_uri | Message::parseRequestUri |
get_message_body_summary | Message::bodySummary |
_caseless_remove | Utils::caselessRemove |
四、URI 附加方法:类型判定、组件操作与引用解析
除标准GuzzleHttp\Psr7\Uri类外,文档还讲解了UriResolver、UriNormalizer两组按 RFC 3986 实现的静态工具。
4.1 URI 类型判定(RFC 3986 4.2)
UriInterface实例可能是绝对 URI 或相对引用。相对引用分为三类:
- network-path 引用:
//example.com/path - absolute-path 引用:
/path - relative-path 引用:
subpath
对应判定方法:
| 方法 | 判定内容 |
|---|---|
Uri::isAbsolute(UriInterface $uri): bool | 是否绝对 URI(含 scheme) |
Uri::isNetworkPathReference(UriInterface $uri): bool | 是否以双斜杠开头 |
Uri::isAbsolutePathReference(UriInterface $uri): bool | 是否以单斜杠开头 |
Uri::isRelativePathReference(UriInterface $uri): bool | 是否不以斜杠开头 |
Uri::isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool | 除 fragment 外与 base 是否完全一致;无 base 时仅空引用成立 |
4.2 URI 组件操作
Uri::isDefaultPort(UriInterface $uri): bool:判断是否使用当前 scheme 的默认端口(独立于具体实现判断getPort()为 null 或标准端口)。Uri::composeComponents($scheme, $authority, $path, $query, $fragment): string:按 RFC 3986 5.3 组装 URI 字符串(通常经__toString间接调用,无需手动调用)。Uri::fromParts(array $parts): UriInterface:由parse_url结果哈希创建 URI。Uri::withQueryValue(UriInterface $uri, $key, $value): UriInterface:设置单个查询值(完全匹配的旧键被替换;值为 null 时输出无值的键,如key)。Uri::withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface:批量设置查询值,行为与withQueryValue()一致。Uri::withoutQueryValue(UriInterface $uri, $key): UriInterface:移除指定查询键。
4.3 UriResolver:引用解析与相对化(RFC 3986 5)
UriResolver::resolve(UriInterface $base, UriInterface $rel): UriInterface:把相对 URI 解析为基于 base 的新 URI,等价于浏览器根据当前请求 URI 解析页面链接的行为。UriResolver::removeDotSegments(string $path): string:按 RFC 3986 5.2.4 移除路径中的./..段。UriResolver::relativize(UriInterface $base, UriInterface $target): UriInterface:resolve()的逆操作,返回 target 相对 base 的引用,满足恒等式(string)$target === (string)UriResolver::resolve($base, UriResolver::relativize($base, $target))。典型用途:以当前请求 URI 为 base,为文档生成相对链接以减小体积或制作自包含归档:
$base = new Uri('http://example.com/a/b/'); echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.4.4 UriNormalizer:规范化与等价比较(RFC 3986 6)
UriNormalizer::normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface返回规范化 URI。scheme 与 host 已按 PSR-7 要求小写化,其余规范化由$flags位掩码控制:
| 常量 | 语义 | 示例 |
|---|---|---|
PRESERVING_NORMALIZATIONS | 默认规范化,仅保留语义 | — |
CAPITALIZE_PERCENT_ENCODING | 百分号编码三元组字母大写 | http://example.org/a%c2%b1b→.../a%C2%B1b |
DECODE_UNRESERVED_CHARACTERS | 解码非保留字符的百分号编码 | .../%7Eusern%61me/→.../~username/ |
CONVERT_EMPTY_PATH | http/https 空路径转为/ | http://example.org→http://example.org/ |
REMOVE_DEFAULT_HOST | 移除默认 host(仅filescheme 默认 host 为localhost) | file://localhost/myfile→file:///myfile |
REMOVE_DEFAULT_PORT | 移除默认端口 | http://example.org:80/→http://example.org/ |
REMOVE_DOT_SEGMENTS | 移除多余点段(相对引用中的点段不删除以免改变语义) | http://example.org/../a/b/../c/./d.html→http://example.org/a/c/d.html |
REMOVE_DUPLICATE_SLASHES | 连续斜杠合并(%2F编码斜杠不处理;可能改变语义) | http://example.org//foo///bar.html→http://example.org/foo/bar.html |
SORT_QUERY_PARAMETERS | 查询参数按键字母排序(参数顺序可能具有语义,不安全) | ?lang=en&article=fred→?article=fred&lang=en |
UriNormalizer::isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool先按给定位掩码规范化再比较;也接受相对引用——此时默认它们会基于同一 base 解析,否则等价性判断无意义。
五、安全、许可与依赖边界
- 安全:包内安全漏洞通过 security@tidelift.com 私下上报,修复公告前请勿公开披露(见 service/php/vendor/guzzlehttp/psr7/README.md 的 Security 一节)。
- 许可:Guzzle PSR-7 采用 MIT 协议,许可文本见 service/php/vendor/guzzlehttp/psr7/LICENSE;注意它与其所属的 AJ-Captcha PHP 主包(GPL-3.0-only,见 service/php/composer.json)协议不同,作为依赖使用时按各自协议约束执行。
- 依赖边界:该包要求 PHP
^7.2.5 || ^8.0,并依赖psr/http-factory、psr/http-message与ralouphie/getallheaders(见 service/php/vendor/guzzlehttp/psr7/composer.json);而 AJ-Captcha 主包要求 PHP>=7.1与 GD、OpenSSL 等扩展。在 ThinkPHP、Laravel 等框架项目中通过composer require fastknife/ajcaptcha安装后,这些依赖会随composer install一并落地(示例工程见 service/php/test/thinkphp 与 service/php/test/laravel)。
结语
Guzzle PSR-7 文档所覆盖的十一类流、Message/Header/Query/Utils/MimeType 静态 API 与 Uri 三件套,构成了 PHP HTTP 客户端生态中最常用的一层消息抽象。对 AJ-Captcha 使用者而言,理解这些底层组件既有助于在验证码服务端集成中排查 HTTP 相关故障,也能在需要流式处理图像响应或构造 HTTP 请求时直接复用这套成熟实现。建议结合 service/php/vendor/guzzlehttp/psr7/src 下的源码(如Utils.php、Message.php、UriResolver.php、UriNormalizer.php)做一次通读,能更快掌握其设计取舍。
- 后端
- 应用安全
- 图像处理
【免费下载链接】captcha
行为验证码(滑动拼图、点选文字),前后端(java)交互,包含h5/Android/IOS/flutter/uni-app的源码和实现
相关推荐
ShowDoc 项目中的 Guzzle PSR-7 消息实现:流、静态 API 与 URI 处理全指南
ShowDoc 项目中的 Guzzle PSR 7 消息实现:流、静态 API 与 URI 处理全指南 本篇技术指南围绕 ShowDoc 项目所依赖的 guzz
文档知识库后端前端Guzzle与PSR-7标准:现代PHP HTTP消息接口最佳实践
Guzzle与PSR 7标准:现代PHP HTTP消息接口最佳实践 你是否还在为PHP项目中的HTTP请求处理感到困扰?不同HTTP客户端库之间的兼容性问题、消
后端Windows 永久激活与 Office 只读一次解决:MAS 四条激活路线完整指南
Windows 永久激活与 Office 只读一次解决:MAS 四条激活路线完整指南 新装 Windows 弹"未激活"、Office 打开是只读模式?开源工具
操作系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考