symplely/http

一个简单的PSR-7请求/响应实现,具有Cookie和会话管理/中间件。

1.1.1 2022-04-16 20:17 UTC

This package is auto-updated.

Last update: 2024-09-17 01:48:34 UTC


README

buildcodecovCodacy BadgeMaintainability

一个完整的 PSR-7 请求/响应实现,包括 CookieSession 管理/中间件。

安装

最好使用 Composer 进行安装。

composer require symplely/http

概述

消息摘要

所有 RequestResponse 类共享一个基类 MessageAbstract,该类提供与消息的头部和主体交互的方法。

以下方法在所有 RequestResponse 对象上可用

getProtocolVersion()

获取HTTP协议版本作为字符串(例如,“1.0”或“1.1”)。

withProtocolVersion($version)

返回一个新的消息实例,其HTTP协议版本指定为字符串(例如,“1.0”或“1.1”)。

getHeaders()

返回与消息相关联的头部的数组。数组的键是头名称,每个值是该头的字符串数组。

hasHeader($name)

对指定的头名称在消息的头部中进行不区分大小写的比较,以查看是否存在。如果找到,则返回 true,如果没有找到,则返回 false

getHeader($name)

返回给定不区分大小写头值的字符串数组。如果头不存在,它将返回一个空数组。

getHeaderLine($name)

返回给定不区分大小写头的所有值的逗号分隔字符串。如果头不存在,它将返回一个空字符串。

withHeader($name, $value)

返回一个新的消息实例,同时替换给定的头与指定的值或值。

<?php

use Async\Http\ServerRequest;

$request = new ServerRequest(...);

$newRequest = $request->withHeader(
    'Content-Type',
    'text/html'
);

$newRequest = $request->withHeader(
    'Accept',
    ['application/json', 'application/xml']
);

withAddedHeader($name, $value)

返回一个新的消息实例,同时添加给定的头与指定的值或值。与 withHeader() 非常相似,但它维护所有现有头。

withoutHeader($name)

返回一个新的消息实例,同时完全删除给定的头。

getBody()

Psr\Http\Message\StreamInterface 格式获取消息的主体。

withBody($body)

返回使用给定主体的新消息实例。主体必须是 Psr\Http\Message\StreamInterface 的实例。

请求

有两种类型的请求:RequestServerRequestRequest 类用于出站请求,例如,您向另一个服务器发送请求。ServerRequest 类用于入站请求,例如,有人为您发送请求以便您处理并响应。

除非您正在构建HTTP客户端,否则您可能只会使用 ServerRequest。两者都包含,因为此库是一个完整的PSR-7实现。

请求

Request 类用于构建出站、客户端请求。请求被认为是不可变的;所有更改请求状态的方法都返回一个包含更改的新实例。原始请求始终保持不变。

构建一个 Request

静态的 Request::create 是构建请求最一致的方法。

<?php

use Async\Http\Request;

$request = Request::create('GET', '/some/path?foo=bar');

或者,您也可以手动构建请求。

<?php

use Async\Http\Request;

$method = 'GET';
$uri = 'http://example.com/';
$headers = ['Content-Type' => 'application/json'];
$body = '{"ping": "pong"}';
$protocolVersion = '1.1';

// All of the parameters are optional.
$request = new Request(
    $method,
    $uri,
    $headers,
    $body,
    $protocolVersion
);

除了从 MessageAbstract 继承的所有方法外,还提供了以下方法

getRequestTarget()

获取请求的目标,客户端将看到它。在大多数情况下,这将是对应的 URI 的原始形式,除非提供了特定的值。例如,如果您请求 "http://example.com/search?q=test",那么这将包含 "/search?q=test")。

withRequestTarget($requestTarget)

返回一个新的实例,该实例的消息请求目标如所给。

getMethod()

获取请求的 HTTP 方法。

withMethod($method)

返回一个新的实例,该实例的消息 HTTP 方法设置为所给。方法名称应该是大写,然而它不会为您纠正大小写。

getUri()

Psr\Http\Message\UriInterface 的形式获取请求的 URI。

withUri($uri, $preserveHost = false)

返回一个新的实例,该实例的消息 URI 设置为所给。必须给出 Psr\Http\Message\UriInterface。如果将 preserve host 设置为 true,则不会更改请求的主机名,除非已经设置了主机名。

ServerRequest

ServerRequest 类扩展了 Request,用于构建传入的服务器端请求。请求被认为是不可变的;所有更改请求状态的方法都会返回一个新的实例,该实例包含更改。原始请求始终保持不变。

构建 ServerRequest

ServerRequestFactory 是构建请求最一致的方法,无论使用的框架是什么。所有 PSR-17 实现都共享此方法签名。

<?php

use Async\Http\ServerRequestFactory;
use Psr\Http\Message\ServerRequestInterface;

$factory = new ServerRequestFactory();

/** @var ServerRequestInterface */
$request = $factory->createServerRequest('GET', '/some/path?foo=bar');
$request = $factory->createServerRequest('GET', '/some/path?foo=bar', $serverParams);

除了从 Request 继承的所有方法外,还提供了以下方法

getServerParams()

获取请求的服务器参数。通常这是 $_SERVER 变量的内容,但不必如此。

getCookieParams()

获取请求的 cookie 参数。返回的结构与 $_COOKIE 提供的格式匹配。

withCookieParams($cookies)

返回一个新的请求实例,该实例的 cookie 参数已更新。$cookies 参数必须与 $_COOKIE 提供的结构匹配。

getQueryParams()

获取请求的查询字符串参数。通常这是 $_GET 变量的内容,但不必如此。查询参数也可能与 URI 查询参数不同步,因为设置一个不会自动设置另一个。

withQueryParams($query)

返回一个新的请求实例,该实例的查询参数已更新。更新查询参数不会自动更新请求的 URI。

getUploadedFiles()

获取一个规范化文件上传的数组,其中数组的每个节点都是一个 Psr\Http\Message\UploadedFileInterface

withUploadedFiles($uploadedFiles)

返回一个新的请求实例,该实例具有给定的文件树。数组的每个节点必须是一个 Psr\Http\Message\UploadedFileInterface

<?php

use Async\Http\ServerRequest;

$request = new ServerRequest(...);

// A simple list.
$newRequest = $request->withUploadedFiles(
    [
        'fileA' => $fileA,
        'fileB' => $fileB,
    ]
);

// A nested list.
$newRequest = $request->withUploadedFiles(
    [
        'images' => [
            'small' => $fileA,
            'large' => $fileB,
        ],
        'foo' => [
            'bar' => [
                'baz' => $fileC,
            ],
        ],
    ]
);

getParsedBody()

获取请求体的参数。如果请求的 Content-Type 是 application/x-www-form-urlencodedmultipart/form-data,并且请求方法是 POST,则此方法将返回一个类似于 $_POST 的数组。对于其他方法,如 PUTPATCH,只有当 Content-Type 是 application/x-www-form-urlencodedapplication/json 时,它才会解析主体,然后返回结果数组。

withParsedBody($body)

返回一个新的带有指定解析体的请求实例。它只接受 arrayobjectnull 值。

getAttributes()

获取与请求关联的所有自定义属性。属性是添加到请求中的应用程序特定数据,可以是任何东西,例如路由数据或认证标志。

getAttribute($name, $default = null)

获取请求的给定属性。如果属性未设置,则返回默认值。

withAttribute($name, $value)

返回一个新的请求实例,其中设置了给定的属性。

<?php

use Async\Http\ServerRequest;

$request = new ServerRequest(...);

// If you have a route such as /product/{id}
// And a request for /product/123
// You can set the 'id' attribute to the product ID
$newRequest = $request->withAttribute('id', 123);

// Some controller for the route
$controller = function ($request) {
    // Look up product data
    $productId = $request->getAttribute('id');
    $product = $someRepository->find($productId);

    // Do something with $product
};

$controller($newRequest);

withoutAttribute($name)

返回一个新的请求实例,其中移除了给定的属性。

响应

有三种响应类可用,主要是为了方便,但它们都扩展了 Response

Response

Response 类用于向客户端返回数据,通常是 HTML 的形式。

构建一个 Response

静态的 Response::create 是构建响应的最一致方法。

<?php

use Async\Http\Response;

$response = new Response();

/** @var ResponseInterface */
$response = Response::create();
$response = Response::create(404);
$response = Response::create(404, 'Not Found');

或者您可以手动构建一个。

<?php

use Async\Http\Response;

// Defaults to a 200 OK response.
$response = new Response('Hello, world!');

// Use a given status code.
$response = new Response(204);

// Send custom headers.
$response = new Response(
    302,
    'Goodbye, world!',
    ['Location' => '/bye-bye']
);

除了从 MessageAbstract 继承的所有方法外,还提供了以下方法

getStatusCode()

可以用来获取响应的 HTTP 状态码(例如,200404)。

getReasonPhrase()

可以用来获取与状态码相关的文本(例如,OKNot Found)。

withStatus()

允许您设置状态以及可选的理由短语,并返回一个新响应对象中的更改。

<?php

use Async\Http\Response;

$response = new Response(...);

$newResponse = $response->withStatus(204);
$newResponse = $response->withStatus(204, 'No Content');

JsonResponse

JsonResponseResponse 类的一个便利扩展,用于简化返回 JSON 数据。它自动将传递给它的任何数据编码为 JSON,并将 Content-Type 头设置为 application/json

<?php

use Async\Http\JsonResponse;

// Defaults to a 200 OK response.
$response = new JsonResponse(['message' => 'Hello, world!']);

// Custom 404 response.
$response = new JsonResponse(
    ['error' => 'Page not found'],
    404
);

// Include additional headers.
$response = new JsonResponse(
    ['error' => 'Invalid credentials'],
    401,
    ['X-Auth' => 'Failed']
);

RedirectResponse

RedirectResponseResponse 类的一个便利扩展,用于简化重定向。它自动设置 Location 头,并在正文中包含一个指向要重定向到的 URI 的链接。

<?php

use Async\Http\RedirectResponse;

// Defaults to a 302 redirect.
$redirect = new RedirectResponse('/some/path');

// Use a given status code.
$redirect = new RedirectResponse('/some/path', 301);

// Send custom headers.
$redirect = new RedirectResponse(
    '/some/path',
    302,
    ['X-Message' => 'Bye-bye']
);

文件上传

UploadedFile 类试图修复 PHP 结构 $_FILES 全局时的问题。

构建一个 UploadedFile

静态的 UploadedFile::create 是构建 UploadedFile 的最一致方法。

<?php

use Async\Http\UploadedFile;

$stream = ...;

$file = UploadedFile::create($stream);
$file = UploadedFile::create($stream, $size, $error, $clientFilename, $clientMediaType);

以下方法可用

getStream()

获取一个表示文件上传的 Psr\Http\Message\StreamInterface

moveTo($targetPath)

将文件移动到目标路径。内部使用 move_uploaded_file()rename(),具体取决于是否在 SAPI 或非 SAPI 环境中调用。

getSize()

获取文件的大小。

getError()

获取与文件相关的任何错误代码。这将返回 UPLOAD_ERR_* 常量 之一。

getClientFilename()

获取客户端发送的文件名。此值的真实性不应被信任,因为它很容易被伪造。

getClientMediaType()

获取客户端发送的媒体类型。此值的真实性不应被信任,因为它很容易被伪造。

流提供了一种标准化的方式来访问可流式传输的数据,例如请求/响应体和文件上传。然而,它们可能在代码的任何其他部分都很有用。

构建一个 Stream

静态的 Stream::createStream::createFromFileStream::createFromResource 是构建 Stream 的最一致方法。

<?php

use Async\Http\Stream;

$stream = Stream::create('string of data');
$stream = Stream::createFromFile('/path/to/file', 'r');

$resource = fopen('/path/to/file', 'wb+');
$stream = Stream::createFromResource($resource);

或者您也可以手动构建一个 Stream

<?php

use Async\Http\Stream;

$stream = new Stream('string of data');
$stream = new Stream($resource);

以下方法可用

close()

关闭流及其任何底层资源。

detach()

将底层资源从流中分离出来并返回它。

getSize()

获取流的尺寸,如果已知。

tell()

返回文件指针的当前位置。

eof()

如果流在流的末尾,则返回 true。

isSeekable()

返回流是否可寻址。

seek($offset, $whence = SEEK_SET)

在流中定位到指定位置。 $whence 应该是 PHP 的 SEEK_* 常量 之一。

rewind()

将流定位到开始位置。

isWritable()

返回流是否可写。

write($string)

将数据写入流。

isReadable()

返回流是否可读。

read($length)

从流中读取数据。

getContents()

返回流中剩余的内容。

getMetadata($key = null)

获取流元数据作为关联数组或检索特定的键。返回的键与 PHP 的 stream_get_meta_data() 函数返回的键相同。

URI

Uri 类使得处理 URI 值变得更加容易,因为你可以轻松地获取或设置 URI 的某些部分。

构建 Uri

静态的 Uri::create 是构建 Uri 最一致的方式。

<?php

use Async\Http\Uri;

$uri = Uri::create('/some/path?foo=bar');
$uri = Uri::create('https://example.com/search?q=test');

或者,你可以手动构建一个 Uri

<?php

use Async\Http\Uri;

$uri = new Uri('/some/path?foo=bar');
$uri = new Uri('https://example.com/search?q=test');

以下方法可用

getScheme()

检索 URI 的方案部分。

getAuthority()

检索 URI 的权限组件。URI 的权限语法为 [用户信息@]主机[:端口]

getUserInfo()

检索 URI 的用户信息组件。语法是 用户名[:密码]

getHost()

检索 URI 的主机部分。

getPort()

检索 URI 的端口部分。如果端口是标准端口(例如,HTTP 的 80 或 HTTPS 的 443),则返回 null

getPath()

检索 URI 的路径部分。

getQuery()

检索 URI 的查询字符串。

getFragment()

检索 URI 的片段部分。

withScheme($scheme)

返回具有指定方案的新的实例。

withUserInfo($user, $password = null)

返回具有指定用户信息的新的实例。

withHost($host)

返回具有指定主机的新的实例。

withPort($port)

返回具有指定端口的新的实例。

withPath($path)

返回具有指定路径的新的实例。

withQuery($query)

返回具有指定查询的新的实例。

withFragment($fragment)

返回具有指定片段的新的实例。

Cookie

Cookies 处理两个问题,管理 Cookie 请求头和管理 Set-Cookie 响应头。它是通过引入一个用于管理 Cookie 实例集合的 Cookies 类和一个用于管理 SetCookie 实例集合的 SetCookies 类来实现的。

这些类是 repo dflydev-fig-cookies 的合并和重构。

实例化这些集合看起来像这样

use Async\Http\Cookies;
use Async\Http\SetCookies;

// Get a collection representing the cookies in the `Cookie` headers
// of a PSR-7 Request.
$cookies = Cookies::fromRequest($request);

// Get a collection representing the cookies in the Set-Cookie headers
// of a PSR-7 Response
$setCookies = SetCookies::fromResponse($response);

在这些集合中以某种方式修改后,它们将被渲染成 PSR-7 请求或 PSR-7 响应,如下所示

// Put the `Cookie` headers and add them to the headers of a
// PSR-7 Request.
$request = $cookies->intoHeader($request);

// Put the `Set-Cookie` headers and add them to the headers of a
// PSR-7 Response.
$response = $setCookies->intoHeader($response);

对于简单的 Cookie 实例,创建。

use Async\Http\Cookie;

// Parse Set-Cookie header(s) and create an instance of CookieInterface.
$cookie = (new Cookie())
    ->create('PHPSESS=1234567890; Domain=domain.tld; Expires=Wed, 21 Oct 2015 07:28:00 GMT; HttpOnly; Max-Age=86400; Path=/admin; Secure');

// After making changes you can just cast it to a RFC-6265 valid string as show below.
$header = (string) $cookie;

类似于 PSR-7 消息,CookieCookiesSetCookieSetCookies 都表示为不可变值对象,所有更改都将返回原始对象的具有所需更改的新实例。

虽然这种设计风格有很多好处,但它可以变得非常冗长。为了解决这个问题,以下提供了两个外观,以尝试简化整个流程并使其更加简洁。

基本用法

开始使用 Cookies 的最简单方法是使用 RequestCookiesResponseCookies 类。它们是原始 Cookies 类的外观。它们的任务是使常见的与 cookie 相关的任务比直接使用原始类更加容易和简洁。

创建 CookiesSetCookies 以及重建 请求响应 都会产生开销。这些方法中的每一个都会经过这个过程,所以在同一代码段中使用过多的这些调用时要小心。在某些情况下,直接使用原始类可能比使用外观类更好。

请求 Cookies

请求包含在 Cookie 请求头中的 cookie 信息。这些头中的 cookie 由 Cookie 类表示。

use Async\Http\Cookie;

$cookie = Cookie::make('theme', 'blue');

为了轻松处理请求 cookie,请使用 RequestCookies 外观。

获取请求 Cookie

get 方法将返回一个 Cookie 实例。如果不存在指定名称的 cookie,则返回的 Cookie 实例将具有 null 值。

get 的可选第三个参数设置当不存在 cookie 时应使用的值。

use Async\Http\RequestCookies;

$cookie = RequestCookies::get($request, 'theme');
$cookie = RequestCookies::get($request, 'theme', 'default-theme');

设置请求 Cookie

set 方法将添加一个 cookie 或替换现有的 cookie。

作为第二个参数使用 Cookie 原始值。

use Async\Http\RequestCookies;

$request = RequestCookies::set($request, Cookie::make('theme', 'blue'));

修改请求 Cookie

modify 方法允许根据当前具有指定名称的 cookie 替换 cookie 的内容。第三个参数是一个接受 Cookie 实例作为第一个参数的 callable,并期望返回一个 Cookie 实例。

如果不存在指定名称的 cookie,则将具有 null 值的新 Cookie 实例传递给 callable。

use Async\Http\RequestCookies;

$modify = function (Cookie $cookie) {
    $value = $cookie->getValue();

    // ... inspect current $value and determine if $value should
    // change or if it can stay the same. in all cases, a cookie
    // should be returned from this callback...
    return $cookie->withValue($value);
}

$request = RequestCookies::modify($request, 'theme', $modify);

删除请求 Cookie

remove 方法如果存在 cookie,则将其删除。

use Async\Http\RequestCookies;

$request = RequestCookies::remove($request, 'theme');

请注意,这不会导致客户端删除 cookie。请参阅 ResponseCookies::expire 来执行此操作。

响应 Cookies

响应包含在 Set-Cookie 响应头中的 cookie 信息。这些头中的 cookie 由 SetCookie 类表示。

use Async\Http\SetCookie;

$setCookie = SetCookie::create('lu')
    ->withValue('Rg3vHJZnehYLjVg7qi3bZjzg')
    ->withExpires('Tue, 15-Jan-2013 21:47:38 GMT')
    ->withMaxAge(500)
    ->rememberForever()
    ->withPath('/')
    ->withDomain('.example.com')
    ->withSecure(true)
    ->withHttpOnly(true)
;

为了轻松处理响应 cookie,请使用 ResponseCookies 外观。

获取响应 Cookie

get 方法将返回一个 SetCookie 实例。如果不存在指定名称的 cookie,则返回的 SetCookie 实例将具有 null 值。

get 的可选第三个参数设置当不存在 cookie 时应使用的值。

use Async\Http\ResponseCookies;

$setCookie = ResponseCookies::get($response, 'theme');
$setCookie = ResponseCookies::get($response, 'theme', 'simple');

设置响应 Cookie

set 方法将添加一个 cookie 或替换现有的 cookie。

作为第二个参数使用 SetCookie 原始值。

use Async\Http\ResponseCookies;

$response = ResponseCookies::set($response, SetCookie::create('token')
    ->withValue('a9s87dfz978a9')
    ->withDomain('example.com')
    ->withPath('/firewall')
);

修改响应 Cookie

modify 方法允许根据当前具有指定名称的 cookie 替换 cookie 的内容。第三个参数是一个接受 SetCookie 实例作为第一个参数的 callable,并期望返回一个 SetCookie 实例。

如果不存在指定名称的 cookie,则将具有 null 值的新 SetCookie 实例传递给 callable。

use Async\Http\ResponseCookies;

$modify = function (SetCookie $setCookie) {
    $value = $setCookie->getValue();

    // ... inspect current $value and determine if $value should
    // change or if it can stay the same. in all cases, a cookie
    // should be returned from this callback...

    return $setCookie
        ->withValue($newValue)
        ->withExpires($newExpires)
    ;
}

$response = ResponseCookies::modify($response, 'theme', $modify);

删除响应 Cookie

remove 方法如果存在 cookie,则从响应中删除 cookie。

use Async\Http\ResponseCookies;

$response = ResponseCookies::remove($response, 'theme');

使响应 Cookie 过期

expire 方法设置一个具有远过去期日期的 cookie。这会导致客户端删除 cookie。

use Async\Http\ResponseCookies;

$response = ResponseCookies::expire($response, 'session_cookie');

会话

通常,PHP 会在调用 session_start() 时自动为您发送头信息。然而,这意味着头信息不是作为 PSR-7 响应对象的一部分发送,因此不在您的控制之下。Sessions 通过将相关头信息放入 PSR-7 响应中来将这些头信息重新置于您的控制之下。

此类是 SessionSessionHeadersHandler 类在仓库中的合并和重写,位于 Relay.Middlewaresessionware

此管理器提供了一个优雅的 OOP API 来访问与会话相关的操作

$session = new \Async\Http\Sessions($id, $cacheLimiter, $cacheExpire);

在实例化时,您可以将缓存限制值作为第二个构造函数参数传入。允许的值有 'nocache'、'public'、'private_no_cache' 或 'private'。如果您完全不需要缓存限制头,请传入一个空字符串 ''。默认值为 'nocache'。

您还可以将缓存过期时间(以分钟为单位)作为第四个构造函数参数传入。默认值为 180 分钟。

  • Sessions::getSession($serverRequest) 从请求中获取会话实例
  • Sessions::start($id) 使用指定 ID 重新启动会话
  • Sessions::toArray() 获取所有会话数据
  • Sessions::getId() 获取会话标识符
  • Sessions::regenerate() 使用加密方法生成会话标识符
  • Sessions::has($item) 验证变量是否已保存到会话中
  • Sessions::set($item, $val) 将变量保存到会话中
  • Sessions::get($item, $default) 从会话中获取变量
  • Sessions::unset($item) 从会话中移除变量
  • Sessions::clear() 移除所有会话变量
  • Sessions::close() 关闭会话并保存其内容,在脚本关闭或 __destruct 时也会自动更新 $_SESSION
  • Sessions::destroy() 销毁会话及其所有内容

会话中间件

相同的实例还附带一个中间件处理器,您可以使用它来自动初始化会话并将会话 cookie 写入响应。

$session = new Sessions();

/**
 * Session is started, populated with default parameters and the response has session cookie header.
 *
 * @param ServerRequestInterface $request
 * @param ResponseInterface $response
 * @param RequestHandlerInterface|callable|null $next
 *
 * The callable should have something similar to this signature:
 * function (ServerRequestInterface $request, ResponseInterface $response) : Response {
 *  // your code
 * }
 */
$response = $session($request, $response, $next);

永远不要 使用 PHP 内置的 session_* 函数(会话对象最终将无法同步)或 $_SESSION 全局变量(更改将被忽略并覆盖)。请使用 Sessions 对象 API

Sessions 实现 IteratorAggregateArrayAccessCountable,因此它看起来非常像 $_SESSION。因此,只需将您的应用程序中所有 $_SESSION 出现的位置替换为对象的实例。

写入会话

$session->abcd = 'efgh';
//or
$session['abcd'] = 'efgh';
//or
$session->set('abcd', 'efgh');

从会话中读取

$abcd =  $session->abc;
//or
$abcd = $session['abcd'];
//or
$abcd = $session->get('abcd');

从会话中移除

unset($session->abc);
//or
unset($session['abcd']);
//or
$session->unset('abcd');

清除会话数据

$session->clear();