提供处理HTTP请求的API。

v4.0.0 2021-05-29 23:25 UTC

This package is auto-updated.

Last update: 2024-08-30 01:13:51 UTC


README

Release Code Quality Code Coverage Packagist

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上使用GETHEAD,永远不应该更改资源。

可以使用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_paramsrequest_paramspath_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

注意:Request实例不同,Response实例是可完全修改的。

<?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-MatchIf-Modified-Since,以及属性modified_timeetag来计算。

头信息

以下是头信息的使用概述,详细信息请参见头信息文档

<?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包定义了以下异常

包定义的异常实现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进行持续测试。

Tests Static Analysis Code Style

行为准则

本项目遵守贡献者行为准则。通过参与本项目及其社区,你应遵守此准则。

贡献

有关详细信息,请参阅CONTRIBUTING

测试

运行 make test-container 来创建并登录到测试容器,然后运行 make test 来运行测试套件。或者,运行 make test-coverage 来以测试覆盖率运行测试套件。打开 build/coverage/index.html 来查看代码覆盖率的详细分解。

许可证

icanboogie/http 采用 BSD-3-Clause 许可证发布。