jasny / http-message
处理 HTTP 请求的 PSR-7 实现
Requires
- php: >=5.6.0
- jasny/php-functions: ^2.2|^3.0|^4.0
- psr/http-message: ^1.0
Requires (Dev)
- jasny/php-code-quality: 2.1.*
- mikey179/vfsstream: ^1.6
Provides
- psr/http-message-implementation: ~1.0.0
- dev-master
- v2.x-dev
- v1.3.8
- v1.3.7
- v1.3.6
- v1.3.5
- v1.3.4
- v1.3.3
- v1.3.2
- v1.3.1
- v1.3.0
- v1.2.3
- v1.2.2
- v1.2.1
- v1.2.0
- v1.1.0
- v1.0.5
- v1.0.4
- v1.0.3
- v1.0.2
- v1.0.1
- v1.0.0
- dev-integration-tests
- dev-force-stale
- dev-reparse-on-null
- dev-global-environment-interface
- dev-scrutinizer-patch-2
- dev-fix-ungroup-uploaded-files-uri
- dev-fix-revive-response
- dev-parsedbody-charset
- dev-global-env-fix
- dev-interface-naming
- dev-stream-eof
- dev-emit
- dev-headers-fix
- dev-response
- dev-globalstream
- dev-scrutinizer-patch-1
This package is auto-updated.
Last update: 2022-06-26 13:47:08 UTC
README
此库提供了对PHP各种超级全局变量的抽象,并控制HTTP响应。这种做法有助于减少消费者对超级全局变量的耦合,并鼓励和促进测试请求消费者。
此库仅实现处理接收到的HTTP请求的PSR-7接口。如果您想向其他Web服务发送HTTP请求,我建议使用Guzzle。
为什么是这个库?
Jasny HTTP Message是一个简洁的实现,可以与任何框架或库一起使用。
该库的重点是按预期行为,没有不希望和意外的副作用。一个好的例子是解析体的实现。
使用库的基本形式保持尽可能简单。除非您需要自定义,否则您只需处理所有可用类的一个子集。
在使用PSR-7时,不允许直接使用echo和header()输出。相反,您需要使用Response对象。使用超级全局变量如$_GET和$_POST也不会起作用,相反,您需要使用ServerRequest对象。
如果您、您的团队或您的项目尚未准备好进行这种范式转变,此库允许您轻松地使用PSR-7。它可以作为正常输入/输出方法和变量(如echo、header()、$_GET、$_POST等)的抽象层。
安装
composer require jasny/http-message
文档
该库实现了以下PSR-7接口
ServerRequest实现Psr\Http\Message\ServerRequestInterfaceResponse实现Psr\Http\Message\ResponseInterfaceStream实现Psr\Http\Message\StreamInterfaceUri实现Psr\Http\Message\UriInterface
它定义了一个接口
ServerRequest
ServerRequest类表示Web服务器接收并经过PHP处理的HTTP请求。
有关ServerRequest的完整文档,请参阅PSR-7 RequestInterface和PSR-7 ServerRequestInterface。
要使用$_SERVER、$_COOKIE、$_GET、$_POST和$_FILES超级全局变量以及以php://input作为输入流创建ServerRequest对象,请使用withGlobalEnvironment()方法。
$request = (new Jasny\HttpMessage\ServerRequest())->withGlobalEnvironment();
绑定到全局环境
通过使用withGlobalEnvironment(true),ServerRequest对象通过引用链接超级全局变量。如果您修改这些变量,更改将反映在ServerRequest对象中。反之,使用withQueryParams()将更改$_GET,withServerParams更改$_SERVER等。
use Jasny\HttpMessage\ServerRequest; // $_GET is not affected $requestByVal = (new ServerRequest())->withGlobalEnvironment(); $requestByVal = $request->withQueryParams(['foo' => 1]); var_dump($_GET); // array(0) { } // $_GET is affected $requestByRef = (new ServerRequest())->withGlobalEnvironment(true); $requestByRef = $request->withQueryParams(['foo' => 1]); var_dump($_GET); // array(1) { ["foo"]=> int(1) }
解析后的主体
getParsedBody()方法可以执行多项操作。
如果显式调用了withParsedBody($data),则提供的数据将始终返回,无论头部或其他请求属性如何。
如果从全局环境复制了$_POST,并且内容类型为multipart/form-data或application/x-www-form-urlencoded,则使用请求数据。
如果请求具有主体内容,并且内容类型为application/json、application/xml或text/xml,则解析主体内容。对于XML,这将产生一个SimpleXmlElement。
如果$_POST没有复制,主体还会解析为application/x-www-form-urlencoded。然而,multipart/form-data永远不会手动解析,因此在这种情况下,如果没有复制$_POST,则抛出异常。
如果内容类型未知,getParsedBody()将简单地返回null。如果主体有内容,但没有设置内容类型头部,则触发警告。
如果头部或主体内容更改,则在调用getParsedBody()时将重新解析主体。然而,这只会在没有使用withParsedBody()显式设置解析后的主体时发生。
响应
Response类允许您创建出站的HTTP响应。
有关Response类的完整文档,请参阅PSR-7 ResponseInterface。
默认情况下,Response对象将流到php://temp并简单地保留所有设置的头部列表。
$response = new Jasny\HttpMessage\Response();
发出
响应对象包含所有输出,包括头部和主体内容。要将其发送到客户端(换句话说,输出它),请使用emit()方法。
use Jasny\HttpMessage\ServerRequest; use Jasny\HttpMessage\Response; $request = (new ServerRequest())->withGlobalEnvironment(); $response = $router->handle($request, new Response()); $response->emit();
emit()方法将创建一个Emitter对象。如果需要,您可以创建自己的类,该类实现EmitterInterface,并将其作为$response->emit(new CustomEmitter())传递。
也可以直接使用发射器而不使用响应的emit()方法。这也很有用,如果您不确定路由器/中间件/控制器是否会返回一个Jasny/HttpMessage/Response或可能返回其他PSR-7 ResponseInterface实现。
use Jasny\HttpMessage\ServerRequest; use Jasny\HttpMessage\Response; use Jasny\HttpMessage\Emitter; $request = (new ServerRequest())->withGlobalEnvironment(); $response = $router->handle($request, new Response()); $emitter = new Emitter(); $emitter->emit($response);
绑定到全局环境
要创建使用header()方法和以php://output作为输出流的Response对象,请使用withGlobalEnvironment(true)方法。
$request = (new Response())->withGlobalEnvironment(true); $request->withHeader('Content-Type', 'text/plain'); // Does `header("Content-Type: text/plain")` $request->getBody()->write('hello world'); // Outputs "hello world"
URI
Uri类旨在根据RFC 3986表示URI。它允许您获取和更改uri的任何特定部分。
有关Uri类的完整文档,请参阅PSR-7 UriInterface。
创建Uri时,您可以传递作为字符串的URL,或者以关联数组的部分形式传递URL。有关URL部分,请参阅parse_url函数。
Jasny\HttpMessage\Uri对象仅支持http和https协议。
$uri = new Jasny\HttpMessage\Uri("http://www.example.com/foo");
流
Stream类是对php流的封装,实现了PSR-7 StreamInterface。
$input = new Jasny\HttpMessage\Stream(); $input->write(json_encode(['foo' => 'bar', 'color' => 'red']));
创建流
默认情况下,它将创建一个使用php://temp的流。在创建流时,您可以传递流资源以使用不同类型的处理。
$handle = fopen('php://memory', 'r+'); $stream = new Jasny\HttpMessage\Stream($handle);
或者,您可以使用Stream::open($uri, $mode)创建具有特定处理的流。
$stream = Jasny\HttpMessage\Stream::open('php://memory', 'r+');
克隆流
在克隆流时,处理程序将被重新创建。这意味着对于php://temp和php://memory,您将得到一个没有任何内容的流。清除响应体的内容通常可以通过克隆流来实现。
$newResponse = $response->withBody(clone $response->getBody());
此行为未在PSR-7中指定,并且克隆流可能与其他PSR-7实现不兼容。
派生属性
您可以使用withAttribute()方法为ServerRequest设置任意属性。要获取属性,请使用getAttribute()方法。
属性可以设置为任何静态值,也可以从ServerRequest对象的其它值派生,例如标题或查询参数。创建派生属性的最简单方法是使用一个Closure。
use Jasny\HttpMessage\ServerRequest; $request = (new ServerRequest())->withAttribute('accept_json', function(ServerRequest $request) { $accept = $request->getHeaderLine('Accept'); return strpos($accept, 'application/json') !== false || strpos($accept, '*/*') !== false; });
您可以通过创建一个实现DerivedAttributeInterface接口的类来创建更复杂的派生属性。实现该接口时,实现__invoke(ServerRequest $request)。
use Jasny\HttpMessage\ServerRequest; use Jasny\HttpMessage\DerivedAttributeInterface; class DetectBot implements DerivedAttributeInterface { public static $identifiers = [ 'google' => 'googlebot', 'yahoo' => 'yahoobot', 'magpie' => 'magpie-crawler' ]; protected $detect = []; public function __construct(array $detect) { $this->detect = $detect; } public function __invoke(ServerRequest $request) { $useragent = $request->getHeaderLine('User-Agent'); $detected = false; foreach ($this->detect as $bot) { $identifier = static::$identifiers[$bot]; $detected = $detected || stripos($useragent, $bot) !== false; } return $detected; } } $request = (new ServerRequest()) ->withAttribute('is_friendly_bot', new DetectBot(['google', 'yahoo'])) ->withAttribute('is_annoying_bot', new DetectBot(['magpie']));
请记住,ServerRequest方法是不可变的,所以withAttribute()将创建一个新的对象。
此库附带了一些派生属性,可以用于。
客户端IP
获取客户端IP。默认情况下,只返回$_SERVER['REMOTE_ADDR']。
use Jasny\HttpMessage\ServerRequest; $request = (new ServerRequest())->withGlobalEnvironment(); $request->getAttribute('client_ip'); // always returns $_SERVER['REMOTE_ADDR']
您可以为受信任的代理指定IP或CIDR地址。当使用时,通过X-Forwarded-For、Client-Ip或Forwarded发送的地址将予以考虑。
use Jasny\HttpMessage\ServerRequest; use Jasny\HttpMessage\DerivedAttribute\ClientIp; $request = (new ServerRequest()) ->withGlobalEnvironment() ->withAttribute('client_ip', new ClientIp(['trusted_proxy => '10.0.0.0/24']); $ip = $request->getAttribute('client_ip'); // for a request from the internal network, use the `X-Forwarded-For` header
注意:如果设置了这些头中的多个,将抛出RuntimeException。这可以防止用户注入一个Client-Ip地址来伪造其IP,而您的代理正在设置X-Forwarded-For头。为了确保不发生此异常,请删除所有意外的转发头。
use Jasny\HttpMessage\ServerRequest; use Jasny\HttpMessage\DerivedAttribute\ClientIp; $request = (new ServerRequest()) ->withGlobalEnvironment() ->withoutHeader('Client-Ip') ->withoutHeader('Forwarded') ->withAttribute('client_ip', new ClientIp(['trusted_proxy' => '10.0.0.0/24']);
IsXhr
测试是否使用AJAX发起的请求。
所有现代浏览器在发起AJAX请求时都将X-Requested-With头设置为XMLHttpRequest。此派生属性简单地检查该头。
use Jasny\HttpMessage\ServerRequest; $request = (new ServerRequest())->withGlobalEnvironment(); $isXhr = $request->getAttribute('is_xhr'); // true or false
LocalReferer
返回Referer头的路径,但只有当引用的方案、主机和端口与请求的方案、主机和端口匹配时。
use Jasny\HttpMessage\ServerRequest; $request = (new ServerRequest())->withGlobalEnvironment(); $back = $request->getAttribute('local_referer') ?: '/'; // Referer Uri path, defaults to `/` for no or external referer
如果需要,可以禁用对方案和/或端口的检查。
use Jasny\HttpMessage\ServerRequest; use Jasny\HttpMessage\DerivedAttribute\LocalReferer; $request = (new ServerRequest()) ->withGlobalEnvironment() ->withAttribute('local_referer', new LocalReferer(['checkScheme' => false, 'checkPort' => false]));
测试
当测试完全符合PSR-7规范的代码时,创建一个带有特定头部、参数和数据的ServerRequest和一个默认的Response。
$request = (new ServerRequest()) ->withMethod('GET') ->withUri('/foo') ->withQueryParams(['page' => 1]);
符合PSR-7规范的代码不得直接访问超全局变量,并且也不得直接输出头部和数据。
测试遗留代码
这个库允许你测试不完全符合PSR-7规范的代码。它可能直接访问超全局变量,并使用echo和headers()进行输出。
// Start output buffering, so the output isn't send directly ob_start(); // Create server request that is bound to the global enviroment. $baseRequest = (new ServerRequest())->withGlobalEnvironment(true); // Modifying the bound request, modifies the superglobals. $request = $baseRequest ->withServerParams(['REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/foo']) ->withQueryParams(['page' => 1]); // Create response that is bound to the global enviroment. $baseResponse = (new Response())->withGlobalEnvironment(true); // Some PSR-7 compatible router handles the request. The code uses `header` and `echo` to output. $router->handle($request, $baseResponse); // Disconnect the global environment, copy the data and headers $response = $response->withoutGlobalEnvironment(); // Refiving the base request and response, restores the global environment. Also clean the output buffer. $baseRequest = $baseRequest->revive(); $baseResponse = $baseResponse->revive()->withBody(new OutputBufferStream()); // Assert response ... // Ready for next request :)
陈旧和复活
使用这项技术,你可以在不重写整个代码库的情况下开始使用PSR-7。相反,你可以逐步重构你的代码。
在进行$copy = $object->with..()时,$copy现在绑定到全局环境,而$object已变得陈旧。
陈旧意味着对象绑定到全局环境,但不再反映当前状态。全局环境的状态已复制到对象中(可以想象它是被冻结在时间中的)。全局环境的变化不会影响陈旧的对象。无法修改陈旧的对象。
请注意,Stream是一个资源,它不会被with...方法克隆。当Response绑定到输出流时也是如此。因此,输出确实会影响陈旧的响应对象。
在某些情况下,你可能想继续使用陈旧的对象。例如,在中间件中捕获错误时。在这种情况下,你需要调用revive()。这个方法将全局环境恢复到陈旧对象的状态。
function errorHandlerMiddleware(ServerRequestInterface $request, ResponseInterface $response, $next) { try { $newResponse = $next($request, $response); } catch (Throwable $error) { // If the next middleware or controller has done something like set the response status, the response is stale. if ($request instanceof Jasny\HttpMessage\ServerRequest) { $request = $request->revive(); } if ($response instanceof Jasny\HttpMessage\Response) { $response = $response->revive(); } $newResponse = handleError($request, $response, $error); } return $newResponse; }
Codeception
如果你使用Codeception,那么Jasny Codeception模块可能很有趣。它使用Jasny Router来处理PSR-7服务器请求。