icanboogie / http
提供处理HTTP请求的API。
Requires
- php: >=7.2
- ext-mbstring: *
- icanboogie/datetime: ^1.2
- icanboogie/event: ^4.0
- icanboogie/prototype: ^5.0
Requires (Dev)
- ext-gd: *
- phpunit/phpunit: ^8.5
README
icanboogie/http包为处理HTTP请求提供了一个基础,包括请求、请求文件、响应和头部的表示。该包还奠定了
以下示例是请求处理概述
<?php namespace ICanBoogie\HTTP; // The request is usually created from the $_SERVER super global. $request = Request::from($_SERVER); /* @var ResponderProvider $responder_provider */ // The Responder Provider matches a request with a Responder $responder = $responder_provider->responder_for_request($request); // The Responder responds to the request with a Response, it might also throw an exception. $response = $responder->respond($request); // The response is sent to the client. $response();
安装
composer require icanboogie/http
请求
一个请求由一个Request实例表示。初始请求通常由$_SERVER
数组创建,而子请求则由Request::OPTION_*
或RequestOptions::OPTION_*
选项的数组创建。
<?php namespace ICanBoogie\HTTP; $initial_request = Request::from($_SERVER); # a custom request in the same environment $request = Request::from('path/to/file.html', $_SERVER); # a request created from scratch $request = Request::from([ Request::OPTION_PATH => 'path/to/file.html', Request::OPTION_IS_LOCAL => true, // or OPTION_IP => '::1' Request::OPTION_METHOD => RequestMethod::METHOD_POST, Request::OPTION_HEADERS => [ 'Cache-Control' => 'no-cache' ] ]);
安全和幂等请求
安全方法是不会修改资源的HTTP方法。例如,在资源URL上使用GET
或HEAD
,永远不应该更改资源。
可以使用is_safe
属性来检查请求是否安全。
<?php use ICanBoogie\HTTP\Request; Request::from([ Request::OPTION_METHOD => Request::METHOD_GET ])->is_safe; // true Request::from([ Request::OPTION_METHOD => Request::METHOD_POST ])->is_safe; // false Request::from([ Request::OPTION_METHOD => Request::METHOD_DELETE ])->is_safe; // false
幂等HTTP方法是可以被多次调用而不产生不同结果的HTTP方法。无论是只调用一次,还是调用十次,结果都应该是相同的。
可以使用is_idempotent
属性来检查请求是否幂等。
<?php use ICanBoogie\HTTP\Request; Request::from([ Request::OPTION_METHOD => Request::METHOD_GET ])->is_idempotent; // true Request::from([ Request::OPTION_METHOD => Request::METHOD_POST ])->is_idempotent; // false Request::from([ Request::OPTION_METHOD => Request::METHOD_DELETE ])->is_idempotent; // true
属性已更改的请求
请求大多数是不可变的,with()
方法创建一个具有更改属性的实例副本。
<?php namespace ICanBoogie\HTTP; $request = Request::from($_SERVER)->with([ Request::OPTION_METHOD => RequestMethod::METHOD_HEAD => true, Request::OPTION_IS_XHR => true ]);
请求参数
无论它们是作为查询字符串的一部分、POST正文还是路径信息发送,随请求发送的参数都收集在数组中。query_params
、request_params
和path_params
属性提供了对这些参数的访问。
您可以通过以下方式访问每种类型的参数
<?php namespace ICanBoogie\HTTP; /* @var $request Request */ $id = $request->query_params['id']; $method = $request->request_params['method']; $info = $request->path_params['info'];
所有请求参数也都通过params
属性提供,该属性合并了查询、请求和路径参数
<?php namespace ICanBoogie\HTTP; /* @var $request Request */ $id = $request->params['id']; $method = $request->params['method']; $info = $request->params['info'];
作为数组使用时,Request实例还提供这些参数,但如果未定义参数,则返回null
<?php namespace ICanBoogie\HTTP; /* @var $request Request */ $id = $request['id']; $method = $request['method']; $info = $request['info']; var_dump($request['undefined']); // null
当然,请求也是一个迭代器
<?php namespace ICanBoogie\HTTP; /* @var $request Request */ foreach ($request as $parameter => $value) { echo "$parameter: $value\n"; }
请求文件
与请求关联的文件收集在FileList实例中。使用$_SERVER
创建的初始请求从$_FILES
中获取其文件。对于自定义请求,文件使用OPTION_FILES
定义。
<?php namespace ICanBoogie\HTTP; $request = Request::from($_SERVER); # or $request = Request::from([ Request::OPTION_FILES => [ 'uploaded' => [ FileOptions::OPTION_PATHNAME => '/path/to/my/example.zip' ] ] ]); # $files = $request->files; // instanceof FileList $file = $files['uploaded']; // instanceof File $file = $files['undefined']; // null
上传的文件和模拟上传的文件由File实例表示。该类尽可能地提供相同的API。使用is_uploaded
属性可以帮助您区分它们。
is_valid
属性是检查文件是否有效的一种简单方法。使用move()
方法可以将文件移出临时文件夹或绕过文件系统。
<?php namespace ICanBoogie\HTTP; /* @var $file File */ echo $file->name; // example.zip echo $file->unsuffixed_name; // example echo $file->extension; // .zip echo $file->size; // 1234 echo $file->type; // application/zip echo $file->is_uploaded; // false if ($file->is_valid) { $file->move('/path/to/repository/' . $file->name, File::MOVE_OVERWRITE); }
match()
方法用于检查文件是否匹配MIME类型、MIME类或文件扩展名
<?php namespace ICanBoogie\HTTP; /* @var $file File */ echo $file->match('application/zip'); // true echo $file->match('application'); // true echo $file->match('.zip'); // true echo $file->match('image/png'); // false echo $file->match('image'); // false echo $file->match('.png'); // false
该方法还处理集合,如果有任何匹配项,则返回true
<?php echo $file->match([ '.png', 'application/zip' ]); // true echo $file->match([ '.png', '.zip' ]); // true echo $file->match([ 'image/png', '.zip' ]); // true echo $file->match([ 'image/png', 'text/plain' ]); // false
可以使用to_array()
方法将File实例转换为数组
<?php $file->to_array(); /* [ 'name' => 'example.zip', 'unsuffixed_name' => 'example', 'extension' => '.zip', 'type' => 'application/zip', 'size' => 1234, 'pathname' => '/path/to/my/example.zip', 'error' => null, 'error_message' => null ] */
请求上下文
由于请求可能嵌套,请求上下文提供了一个安全的地方来存储与应用程序状态相关的状态,例如请求相关的站点、页面、路由、调度器等。上下文可以用作数组,但也是一个原型实例。
以下示例演示了如何在请求上下文中存储一个值
<?php namespace ICanBoogie\HTTP; $request = Request::from($_SERVER); $request->context['site'] = $app->models['sites']->one;
以下示例演示了如何使用原型功能在请求上下文中提供值
<?php namespace ICanBoogie\HTTP; use ICanBoogie\HTTP\Request\Context; use ICanBoogie\Prototype; Prototype::from(Context::class)['lazy_get_site'] = function(Context $context) use ($site_model) { return $site_model->resolve_from_request($context->request); }; $request = Request::from($_SERVER); $site = $request->context['site']; # or $site = $request->context->site;
获取响应
通过调用请求或调用可用的HTTP方法之一来获取响应。使用dispatch()
辅助器来调度请求。如果调度成功,则返回一个Response实例,否则抛出NotFound异常。
<?php namespace ICanBoogie\HTTP; /* @var $request Request */ $response = $request(); # using the POST method and additional parameters $response = $request->post([ 'param' => 'value' ]);
响应
请求的响应由一个Response实例表示。响应体可以是null
、字符串(或Stringable
)或Closure
。
<?php namespace ICanBoogie\HTTP; $response = new Response('<!DOCTYPE html><html><body><h1>Hello world!</h1></body></html>', Response::STATUS_OK, [ Headers::HEADER_CONTENT_TYPE => 'text/html', Headers::HEADER_CACHE_CONTROL => 'public, max-age=3600', ]);
通过调用响应来发送头和体
<?php namespace ICanBoogie\HTTP; /* @var $response Response */ $response();
响应状态
响应状态由一个Status实例表示。它可以定义为HTTP响应代码,如200
,一个数组如[ 200, "Ok" ]
,或一个字符串如"200 Ok"
。
<?php namespace ICanBoogie\HTTP; $response = new Response; echo $response->status; // 200 Ok echo $response->status->code; // 200 echo $response->status->message; // Ok $response->status->is_valid; // true $response->status = Response::STATUS_NOT_FOUND; echo $response->status->code; // 404 echo $response->status->message; // Not Found $response->status->is_valid; // false $response->status->is_client_error; // true $response->status->is_not_found; // true
流式传输响应体
当需要流式传输大型响应体时,建议使用闭包作为响应体,而不是消耗大量内存的大字符串。
<?php namespace ICanBoogie\HTTP; $records = $app->models->order('created_at DESC'); $output = function() use ($records) { $out = fopen('php://output', 'w'); foreach ($records as $record) { fputcsv($out, [ $record->title, $record->created_at ]); } fclose($out); }; $response = new Response($output, Response::STATUS_OK, [ 'Content-Type' => 'text/csv' ]);
关于Content-Length
头字段
在v2.3.2之前,当可计算时(例如,当主体是字符串或实现__toString()
的实例时),会自动添加Content-Length
头字段。从v2.3.2开始,这不再是这种情况,并且当需要时必须定义头字段。这是为了防止Apache+FastCGI+DEFLATE中的bug,即尽管主体被压缩,但Content-Length
字段未调整。此外,在大多数情况下,为生成的内容定义该字段并不是一个好主意,因为它会阻止响应作为分块传输编码发送。
重定向响应
可以使用RedirectResponse实例创建重定向响应。
<?php namespace ICanBoogie\HTTP; $response = new RedirectResponse('/to/redirect/location'); $response->status->code; // 302 $response->status->is_redirect; // true
交付文件
可以使用FileResponse实例交付文件。缓存控制和范围请求会自动处理,您只需提供文件的路径名或SplFileInfo
实例以及一个请求即可。
<?php namespace ICanBoogie\HTTP; /* @var $request Request */ $response = new FileResponse("/absolute/path/to/my/file", $request); $response();
可以使用OPTION_FILENAME
选项强制下载。当然,utf-8字符串也受到支持。
<?php namespace ICanBoogie\HTTP; /* @var $request Request */ $response = new FileResponse("/absolute/path/to/my/file", $request, [ FileResponse::OPTION_FILENAME => "Vidéo d'un été à la mer.mp4" ]); $response();
以下选项也可用
-
OPTION_ETAG
:指定响应的ETag
头字段。如果没有定义,则使用文件的SHA-384
代替。 -
OPTION_EXPIRES
:指定过期日期,可以是DateTime
实例或相对日期(例如 "+3 month"),这映射到Expires
头字段。从当前时间计算Cache-Control
头字段的max-age
指令。如果没有定义,则使用DEFAULT_EXPIRES
("+1 month")。 -
OPTION_MIME
:指定文件的MIME类型,这映射到Content-Type
头字段。如果没有定义,则使用finfo::file()
来猜测MIME类型。
以下属性可用
-
modified_time
:返回文件的最后修改时间戳。 -
is_modified
:文件自上次响应以来是否被修改。该值使用请求头字段If-None-Match
和If-Modified-Since
,以及属性modified_time
和etag
来计算。
头信息
以下是头信息的使用概述,详细信息请参见头信息文档。
<?php namespace ICanBoogie\HTTP; $headers = new Headers(); $headers->cache_control = 'public, max-age=3600, no-transform'; $headers->cache_control->no_transform = false; $headers->cache_control->max_age = 7200; echo $headers->cache_control; // public, max-age=7200 $headers->content_type = 'text/plain'; $headers->content_type->type = 'application/xml'; $headers->content_type->charset = 'utf-8'; echo $headers->content_type; // application/xml; charset=utf-8 $headers->content_length = 123; $headers->content_disposition->type = 'attachment'; $headers->content_disposition->filename = 'été.jpg'; echo $headers->content_disposition; // attachment; filename="ete.jpg"; filename*=UTF-8''%C3%A9t%C3%A9.jpg $headers->etag = "ABC123"; $headers->date = 'now'; $headers->expires = '+1 hour'; $headers->if_modified_since = '-1 hour'; $headers->if_unmodified_since = '-1 hour'; $headers->last_modified = '2022-01-01'; $headers->retry_after = '+1 hour'; $headers->retry_after = 123; $headers->location = 'to/the/moon'; $headers['X-My-Header'] = 'Some value'; echo $headers['X-My-Header']; // 'Some value';
异常
HTTP包定义了以下异常
- ClientError:当发生客户端错误时抛出。
- NotFound:当资源未找到时抛出。例如,当分发器无法将请求解析为响应时,将抛出此异常。
- AuthenticationRequired:当需要客户端身份验证时抛出。实现SecurityError。
- PermissionRequired:当客户端缺少必需的权限时抛出。实现SecurityError。
- MethodNotAllowed:当不支持HTTP方法时抛出。
- ServerError:当发生服务器错误时抛出。
- ServiceUnavailable:当服务器当前不可用(因为过载或维护)时抛出。
- ForceRedirect:当必须进行重定向时抛出。
- StatusCodeNotValid:当HTTP状态码无效时抛出。
包定义的异常实现ICanBoogie\HTTP\Exception
接口。使用此接口可以轻松捕获与HTTP相关的异常
<?php try { // … } catch (\ICanBoogie\HTTP\Exception $e) { // HTTP exception types } catch (\Exception $e) { // Other exception types }
辅助函数
以下辅助函数可用
- [
dispatch()
][]:使用由 [get_dispatcher()
][] 返回的分发器来分发请求。 - [
get_dispatcher()
][]:返回请求分发器。如果没有定义分发器提供程序,则该方法定义一个新实例的 [DispatcherProvider][] 作为提供程序,并使用它来检索分发器。 get_initial_request()
:返回初始请求。
<?php namespace ICanBoogie\HTTP; $request = get_initial_request(); $response = dispatch($request);
持续集成
项目通过GitHub actions进行持续测试。
行为准则
本项目遵守贡献者行为准则。通过参与本项目及其社区,你应遵守此准则。
贡献
有关详细信息,请参阅CONTRIBUTING。
测试
运行 make test-container
来创建并登录到测试容器,然后运行 make test
来运行测试套件。或者,运行 make test-coverage
来以测试覆盖率运行测试套件。打开 build/coverage/index.html
来查看代码覆盖率的详细分解。
许可证
icanboogie/http 采用 BSD-3-Clause 许可证发布。