From 60b7442bc1c3f4b71c1b621c03a3ece4a5d51bc3 Mon Sep 17 00:00:00 2001 From: chengyao <64066545+topyao@users.noreply.github.com> Date: Mon, 15 Aug 2022 19:10:47 +0800 Subject: [PATCH 01/11] =?UTF-8?q?Pref:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/framework/src/Di/Annotation/Inject.php | 4 +- .../src/Contract/HeaderInterface.php | 21 ++++++---- .../src/Middleware/AllowCrossDomain.php | 38 ++++++++++++++----- .../Middleware/ExceptionHandleMiddleware.php | 3 +- .../ResponseEmitter/FPMResponseEmitter.php | 5 ++- .../ResponseEmitter/SwooleResponseEmitter.php | 31 +++++++-------- .../WorkerManResponseEmitter.php | 7 ++-- 7 files changed, 67 insertions(+), 42 deletions(-) diff --git a/src/framework/src/Di/Annotation/Inject.php b/src/framework/src/Di/Annotation/Inject.php index fd22c24a..891ae708 100644 --- a/src/framework/src/Di/Annotation/Inject.php +++ b/src/framework/src/Di/Annotation/Inject.php @@ -22,10 +22,10 @@ class Inject implements PropertyAnnotation { /** - * @param null|string $id 注入的类型 + * @param string $id 注入的类型 */ public function __construct( - protected ?string $id = null + protected string $id = '' ) { } diff --git a/src/http-message/src/Contract/HeaderInterface.php b/src/http-message/src/Contract/HeaderInterface.php index f680209a..40bf4d61 100644 --- a/src/http-message/src/Contract/HeaderInterface.php +++ b/src/http-message/src/Contract/HeaderInterface.php @@ -4,11 +4,18 @@ interface HeaderInterface { - public const HEADER_CONTENT_TYPE = 'Content-Type'; - public const HEADER_SET_COOKIE = 'Set-Cookie'; - public const HEADER_PRAGMA = 'Pragma'; - public const HEADER_EXPIRES = 'Expires'; - public const HEADER_CACHE_CONTROL = 'Cache-Control'; - public const HEADER_CONTENT_TRANSFER_ENCODING = 'Content-Transfer-Encoding'; - public const HEADER_CONTENT_DISPOSITION = 'Content-Disposition'; + public const HEADER_CONTENT_TYPE = 'Content-Type'; + public const HEADER_SET_COOKIE = 'Set-Cookie'; + public const HEADER_PRAGMA = 'Pragma'; + public const HEADER_ACCEPT = 'Accept'; + public const HEADER_EXPIRES = 'Expires'; + public const HEADER_CACHE_CONTROL = 'Cache-Control'; + public const HEADER_CONTENT_TRANSFER_ENCODING = 'Content-Transfer-Encoding'; + public const HEADER_CONTENT_DISPOSITION = 'Content-Disposition'; + public const HEADER_ORIGIN = 'Origin'; + public const HEADER_ACCESS_CONTROL_ALLOW_ORIGIN = 'Access-Control-Allow-Origin'; + public const HEADER_ACCESS_CONTROL_MAX_AGE = 'Access-Control-Max-Age'; + public const HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS = 'Access-Control-Allow-Credentials'; + public const HEADER_ACCESS_CONTROL_ALLOW_METHODS = 'Access-Control-Allow-Methods'; + public const HEADER_ACCESS_CONTROL_ALLOW_HEADERS = 'Access-Control-Allow-Headers'; } diff --git a/src/http-server/src/Middleware/AllowCrossDomain.php b/src/http-server/src/Middleware/AllowCrossDomain.php index a97b2a2b..1ef01cf3 100644 --- a/src/http-server/src/Middleware/AllowCrossDomain.php +++ b/src/http-server/src/Middleware/AllowCrossDomain.php @@ -11,12 +11,18 @@ namespace Max\Http\Server\Middleware; +use Max\Http\Message\Contract\HeaderInterface; +use Max\Http\Message\Contract\RequestMethodInterface; +use Max\Http\Message\Contract\StatusCodeInterface; use Max\Http\Message\Response; +use Max\Utils\Str; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; +use function Max\Utils\collect; + class AllowCrossDomain implements MiddlewareInterface { /** @var array 允许域,全部可以使用`*` */ @@ -24,20 +30,19 @@ class AllowCrossDomain implements MiddlewareInterface /** @var array 附加的响应头 */ protected array $addedHeaders = [ - 'Access-Control-Allow-Credentials' => 'true', - 'Access-Control-Max-Age' => 1800, - 'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE, OPTIONS', - 'Access-Control-Allow-Headers' => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With', + HeaderInterface::HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS => 'true', + HeaderInterface::HEADER_ACCESS_CONTROL_MAX_AGE => 1800, + HeaderInterface::HEADER_ACCESS_CONTROL_ALLOW_METHODS => 'GET, POST, PATCH, PUT, DELETE, OPTIONS', + HeaderInterface::HEADER_ACCESS_CONTROL_ALLOW_HEADERS => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With', ]; public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - $allowOrigin = in_array('*', $this->allowOrigin) ? '*' : $request->getHeaderLine('Origin'); - if ($allowOrigin !== '') { - $headers = $this->addedHeaders; - $headers['Access-Control-Allow-Origin'] = $allowOrigin; - if (strcasecmp($request->getMethod(), 'OPTIONS') === 0) { - return new Response(204, $headers); + if ($this->shouldCrossOrigin($origin = $request->getHeaderLine(HeaderInterface::HEADER_ORIGIN))) { + $headers = $this->addedHeaders; + $headers[HeaderInterface::HEADER_ACCESS_CONTROL_ALLOW_ORIGIN] = $origin; + if (strcasecmp($request->getMethod(), RequestMethodInterface::METHOD_OPTIONS) === 0) { + return new Response(StatusCodeInterface::STATUS_NO_CONTENT, $headers); } $response = $handler->handle($request); foreach ($headers as $name => $header) { @@ -48,4 +53,17 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface return $handler->handle($request); } + + /** + * 允许跨域 + */ + protected function shouldCrossOrigin(string $origin) + { + if (empty($origin)) { + return false; + } + return collect($this->allowOrigin)->first(function($allowOrigin) use ($origin) { + return Str::is($allowOrigin, $origin); + }); + } } diff --git a/src/http-server/src/Middleware/ExceptionHandleMiddleware.php b/src/http-server/src/Middleware/ExceptionHandleMiddleware.php index 7a01bbd3..b3423a44 100644 --- a/src/http-server/src/Middleware/ExceptionHandleMiddleware.php +++ b/src/http-server/src/Middleware/ExceptionHandleMiddleware.php @@ -11,6 +11,7 @@ namespace Max\Http\Server\Middleware; +use Max\Http\Message\Contract\HeaderInterface; use Max\Http\Message\Exception\HttpException; use Max\Http\Message\Response; use Psr\Http\Message\ResponseInterface; @@ -49,7 +50,7 @@ protected function renderException(Throwable $throwable, ServerRequestInterface { $message = $throwable->getMessage(); $statusCode = $this->getStatusCode($throwable); - if (str_contains($request->getHeaderLine('Accept'), 'application/json') + if (str_contains($request->getHeaderLine(HeaderInterface::HEADER_ACCEPT), 'application/json') || strcasecmp('XMLHttpRequest', $request->getHeaderLine('X-REQUESTED-WITH')) === 0) { return new Response($statusCode, [], json_encode([ 'status' => false, diff --git a/src/http-server/src/ResponseEmitter/FPMResponseEmitter.php b/src/http-server/src/ResponseEmitter/FPMResponseEmitter.php index b8efe583..353f2e50 100644 --- a/src/http-server/src/ResponseEmitter/FPMResponseEmitter.php +++ b/src/http-server/src/ResponseEmitter/FPMResponseEmitter.php @@ -11,6 +11,7 @@ namespace Max\Http\Server\ResponseEmitter; +use Max\Http\Message\Contract\HeaderInterface; use Max\Http\Message\Cookie; use Max\Http\Server\Contract\ResponseEmitterInterface; use Psr\Http\Message\ResponseInterface; @@ -20,7 +21,7 @@ class FPMResponseEmitter implements ResponseEmitterInterface public function emit(ResponseInterface $psrResponse, $sender = null) { header(sprintf('HTTP/%s %d %s', $psrResponse->getProtocolVersion(), $psrResponse->getStatusCode(), $psrResponse->getReasonPhrase()), true); - foreach ($psrResponse->getHeader('Set-Cookie') as $cookie) { + foreach ($psrResponse->getHeader(HeaderInterface::HEADER_SET_COOKIE) as $cookie) { $cookie = Cookie::parse($cookie); setcookie( $cookie->getName(), @@ -32,7 +33,7 @@ public function emit(ResponseInterface $psrResponse, $sender = null) $cookie->isHttponly() ); } - $psrResponse = $psrResponse->withoutHeader('Set-Cookie'); + $psrResponse = $psrResponse->withoutHeader(HeaderInterface::HEADER_SET_COOKIE); foreach ($psrResponse->getHeaders() as $name => $value) { header($name . ': ' . implode(', ', $value)); } diff --git a/src/http-server/src/ResponseEmitter/SwooleResponseEmitter.php b/src/http-server/src/ResponseEmitter/SwooleResponseEmitter.php index 53fa504d..7aa323f7 100644 --- a/src/http-server/src/ResponseEmitter/SwooleResponseEmitter.php +++ b/src/http-server/src/ResponseEmitter/SwooleResponseEmitter.php @@ -11,6 +11,7 @@ namespace Max\Http\Server\ResponseEmitter; +use Max\Http\Message\Contract\HeaderInterface; use Max\Http\Message\Cookie; use Max\Http\Message\Stream\FileStream; use Max\Http\Server\Contract\ResponseEmitterInterface; @@ -25,10 +26,20 @@ class SwooleResponseEmitter implements ResponseEmitterInterface public function emit(ResponseInterface $psrResponse, $sender = null) { $sender->status($psrResponse->getStatusCode(), $psrResponse->getReasonPhrase()); - foreach ($psrResponse->getHeader('Set-Cookie') as $cookie) { - $this->sendCookie(Cookie::parse($cookie), $sender); + foreach ($psrResponse->getHeader(HeaderInterface::HEADER_SET_COOKIE) as $cookieLine) { + $cookie = Cookie::parse($cookieLine); + $sender->cookie( + $cookie->getName(), + $cookie->getValue(), + $cookie->getExpires(), + $cookie->getPath(), + $cookie->getDomain(), + $cookie->isSecure(), + $cookie->isHttponly(), + $cookie->getSameSite() + ); } - $psrResponse = $psrResponse->withoutHeader('Set-Cookie'); + $psrResponse = $psrResponse->withoutHeader(HeaderInterface::HEADER_SET_COOKIE); foreach ($psrResponse->getHeaders() as $key => $value) { $sender->header($key, implode(', ', $value)); } @@ -42,18 +53,4 @@ public function emit(ResponseInterface $psrResponse, $sender = null) } $body?->close(); } - - protected function sendCookie(Cookie $cookie, Response $response): void - { - $response->cookie( - $cookie->getName(), - $cookie->getValue(), - $cookie->getExpires(), - $cookie->getPath(), - $cookie->getDomain(), - $cookie->isSecure(), - $cookie->isHttponly(), - $cookie->getSameSite() - ); - } } diff --git a/src/http-server/src/ResponseEmitter/WorkerManResponseEmitter.php b/src/http-server/src/ResponseEmitter/WorkerManResponseEmitter.php index 04123f59..8a276148 100644 --- a/src/http-server/src/ResponseEmitter/WorkerManResponseEmitter.php +++ b/src/http-server/src/ResponseEmitter/WorkerManResponseEmitter.php @@ -11,6 +11,7 @@ namespace Max\Http\Server\ResponseEmitter; +use Max\Http\Message\Contract\HeaderInterface; use Max\Http\Message\Cookie; use Max\Http\Message\Stream\FileStream; use Max\Http\Server\Contract\ResponseEmitterInterface; @@ -26,8 +27,8 @@ class WorkerManResponseEmitter implements ResponseEmitterInterface public function emit(ResponseInterface $psrResponse, $sender = null) { $response = new Response($psrResponse->getStatusCode()); - $cookies = $psrResponse->getHeader('Set-Cookie'); - $psrResponse = $psrResponse->withoutHeader('Set-Cookie'); + $cookies = $psrResponse->getHeader(HeaderInterface::HEADER_SET_COOKIE); + $psrResponse = $psrResponse->withoutHeader(HeaderInterface::HEADER_SET_COOKIE); foreach ($psrResponse->getHeaders() as $name => $values) { $response->header($name, implode(', ', $values)); } @@ -49,7 +50,7 @@ public function emit(ResponseInterface $psrResponse, $sender = null) $cookie->getSameSite() ); } - $sender->send($response->withBody((string) $body?->getContents())); + $sender->send($response->withBody((string)$body?->getContents())); } $body?->close(); $sender->close(); From c2f2e0e24e98eb4c7dfeeb2d5be1b619314c388a Mon Sep 17 00:00:00 2001 From: chengyao <64066545+topyao@users.noreply.github.com> Date: Tue, 16 Aug 2022 13:40:26 +0800 Subject: [PATCH 02/11] =?UTF-8?q?Pref:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Middleware/AllowCrossDomain.php | 31 ++++++++++++++----- .../src/Middleware/SessionMiddleware.php | 11 ++++++- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/http-server/src/Middleware/AllowCrossDomain.php b/src/http-server/src/Middleware/AllowCrossDomain.php index 1ef01cf3..9eb2b1a9 100644 --- a/src/http-server/src/Middleware/AllowCrossDomain.php +++ b/src/http-server/src/Middleware/AllowCrossDomain.php @@ -39,21 +39,38 @@ class AllowCrossDomain implements MiddlewareInterface public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { if ($this->shouldCrossOrigin($origin = $request->getHeaderLine(HeaderInterface::HEADER_ORIGIN))) { - $headers = $this->addedHeaders; - $headers[HeaderInterface::HEADER_ACCESS_CONTROL_ALLOW_ORIGIN] = $origin; + $headers = $this->createCORSHeaders($origin); if (strcasecmp($request->getMethod(), RequestMethodInterface::METHOD_OPTIONS) === 0) { return new Response(StatusCodeInterface::STATUS_NO_CONTENT, $headers); } - $response = $handler->handle($request); - foreach ($headers as $name => $header) { - $response = $response->withHeader($name, $header); - } - return $response; + + return $this->addHeadersToResponse($handler->handle($request), $headers); } return $handler->handle($request); } + /** + * 创建响应头部 + */ + protected function createCORSHeaders(string $origin): array + { + $headers = $this->addedHeaders; + $headers[HeaderInterface::HEADER_ACCESS_CONTROL_ALLOW_ORIGIN] = $origin; + return $headers; + } + + /** + * 将头部添加到响应 + */ + protected function addHeadersToResponse(ResponseInterface $response, array $headers): ResponseInterface + { + foreach ($headers as $name => $header) { + $response = $response->withHeader($name, $header); + } + return $response; + } + /** * 允许跨域 */ diff --git a/src/http-server/src/Middleware/SessionMiddleware.php b/src/http-server/src/Middleware/SessionMiddleware.php index 1ae9c0c5..474050f2 100644 --- a/src/http-server/src/Middleware/SessionMiddleware.php +++ b/src/http-server/src/Middleware/SessionMiddleware.php @@ -59,7 +59,16 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $response = $handler->handle($request); $session->save(); $session->close(); - $cookie = new Cookie($this->name, $session->getId(), time() + $this->expires, $this->path, $this->domain, $this->secure, $this->httponly); + + return $this->addCookieToResponse($response, $this->name, $session->getId()); + } + + /** + * 将cookie添加到响应 + */ + protected function addCookieToResponse(ResponseInterface $response, string $name, string $value): ResponseInterface + { + $cookie = new Cookie($name, $value, time() + $this->expires, $this->path, $this->domain, $this->secure, $this->httponly); return $response->withAddedHeader(HeaderInterface::HEADER_SET_COOKIE, $cookie->__toString()); } From aa62b043fdab4ff52528e892dfd787db0c5e994d Mon Sep 17 00:00:00 2001 From: chengyao <64066545+topyao@users.noreply.github.com> Date: Tue, 16 Aug 2022 13:48:42 +0800 Subject: [PATCH 03/11] =?UTF-8?q?Pref:=20parallel=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Exception/ParallelExecutionException.php | 2 +- src/{utils => swoole}/src/Parallel.php | 23 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) rename src/{utils => swoole}/src/Exception/ParallelExecutionException.php (95%) rename src/{utils => swoole}/src/Parallel.php (82%) diff --git a/src/utils/src/Exception/ParallelExecutionException.php b/src/swoole/src/Exception/ParallelExecutionException.php similarity index 95% rename from src/utils/src/Exception/ParallelExecutionException.php rename to src/swoole/src/Exception/ParallelExecutionException.php index 53d0605d..4cce41dc 100644 --- a/src/utils/src/Exception/ParallelExecutionException.php +++ b/src/swoole/src/Exception/ParallelExecutionException.php @@ -9,7 +9,7 @@ * @license https://github.com/marxphp/max/blob/master/LICENSE */ -namespace Max\Utils\Exception; +namespace Max\Swoole\Exception; class ParallelExecutionException extends \RuntimeException { diff --git a/src/utils/src/Parallel.php b/src/swoole/src/Parallel.php similarity index 82% rename from src/utils/src/Parallel.php rename to src/swoole/src/Parallel.php index 1bfdb997..5ef01c47 100644 --- a/src/utils/src/Parallel.php +++ b/src/swoole/src/Parallel.php @@ -9,24 +9,23 @@ * @license https://github.com/marxphp/max/blob/master/LICENSE */ -namespace Max\Utils; +namespace Max\Swoole; -use Max\Utils\Exception\ParallelExecutionException; +use Max\Swoole\Exception\ParallelExecutionException; use Swoole\Coroutine; use Swoole\Coroutine\Channel; use Swoole\Coroutine\WaitGroup; +use Throwable; + +use function sprintf; class Parallel { /** * @var callable[] */ - private array $callbacks = []; - - /** - * @var null|Channel - */ - private $concurrentChannel; + private array $callbacks = []; + private ?Channel $concurrentChannel; /** * @param int $concurrent if $concurrent is equal to 0, that means unlimit @@ -55,10 +54,10 @@ public function wait(bool $throw = true): array $wg->add(count($this->callbacks)); foreach ($this->callbacks as $key => $callback) { $this->concurrentChannel && $this->concurrentChannel->push(true); - Coroutine::create(function () use ($callback, $key, $wg, &$result, &$throwables) { + Coroutine::create(function() use ($callback, $key, $wg, &$result, &$throwables) { try { $result[$key] = call($callback); - } catch (\Throwable $throwable) { + } catch (Throwable $throwable) { $throwables[$key] = $throwable; } finally { $this->concurrentChannel && $this->concurrentChannel->pop(); @@ -90,13 +89,13 @@ public function clear(): void /** * Format throwables into a nice list. * - * @param \Throwable[] $throwables + * @param Throwable[] $throwables */ private function formatThrowables(array $throwables): string { $output = ''; foreach ($throwables as $key => $value) { - $output .= \sprintf('(%s) %s: %s' . PHP_EOL . '%s' . PHP_EOL, $key, get_class($value), $value->getMessage(), $value->getTraceAsString()); + $output .= sprintf('(%s) %s: %s' . PHP_EOL . '%s' . PHP_EOL, $key, get_class($value), $value->getMessage(), $value->getTraceAsString()); } return $output; } From 3a015f0f24ac17de71e27260ff44bba600fcbca8 Mon Sep 17 00:00:00 2001 From: chengyao <64066545+topyao@users.noreply.github.com> Date: Tue, 16 Aug 2022 13:49:02 +0800 Subject: [PATCH 04/11] Add: swoole context and coroutine --- src/swoole/src/Context.php | 24 ++++++++++++++++++++++++ src/swoole/src/Coroutine.php | 11 +++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/swoole/src/Context.php create mode 100644 src/swoole/src/Coroutine.php diff --git a/src/swoole/src/Context.php b/src/swoole/src/Context.php new file mode 100644 index 00000000..01666291 --- /dev/null +++ b/src/swoole/src/Context.php @@ -0,0 +1,24 @@ + Date: Tue, 16 Aug 2022 18:48:39 +0800 Subject: [PATCH 05/11] =?UTF-8?q?Add:=20=E6=B7=BB=E5=8A=A0samesite?= =?UTF-8?q?=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Middleware/SessionMiddleware.php | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/http-server/src/Middleware/SessionMiddleware.php b/src/http-server/src/Middleware/SessionMiddleware.php index 474050f2..880d6e26 100644 --- a/src/http-server/src/Middleware/SessionMiddleware.php +++ b/src/http-server/src/Middleware/SessionMiddleware.php @@ -26,21 +26,16 @@ class SessionMiddleware implements MiddlewareInterface /** * Cookie 过期时间【+9小时,实际1小时后过期,和时区有关】. */ - protected int $expires = 9 * 3600; - - protected string $name = 'MAXPHP_SESSION_ID'; - - protected bool $httponly = true; - - protected string $path = '/'; - - protected string $domain = ''; - - protected bool $secure = true; - + protected int $expires = 9 * 3600; /** - * @var mixed|SessionHandlerInterface + * 会话Cookie名 */ + protected string $name = 'MAXPHP_SESSION_ID'; + protected bool $httponly = true; + protected string $path = '/'; + protected string $domain = ''; + protected bool $secure = true; + protected string $sameSite = 'lax'; protected SessionHandlerInterface $handler; public function __construct(ConfigInterface $config) @@ -68,7 +63,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface */ protected function addCookieToResponse(ResponseInterface $response, string $name, string $value): ResponseInterface { - $cookie = new Cookie($name, $value, time() + $this->expires, $this->path, $this->domain, $this->secure, $this->httponly); + $cookie = new Cookie($name, $value, time() + $this->expires, $this->path, $this->domain, $this->secure, $this->httponly, $this->sameSite); return $response->withAddedHeader(HeaderInterface::HEADER_SET_COOKIE, $cookie->__toString()); } From 7ff785e8f65cc59a9d48329a4dfd20d40982f1df Mon Sep 17 00:00:00 2001 From: chengyao <64066545+topyao@users.noreply.github.com> Date: Tue, 16 Aug 2022 18:48:55 +0800 Subject: [PATCH 06/11] =?UTF-8?q?Docs:=20=E6=B7=BB=E5=8A=A0=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/http-message/src/Cookie.php | 84 ++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/src/http-message/src/Cookie.php b/src/http-message/src/Cookie.php index b7cd40c4..ee7b57ce 100644 --- a/src/http-message/src/Cookie.php +++ b/src/http-message/src/Cookie.php @@ -30,6 +30,51 @@ public function __construct( } } + /** + * 解析Cookie字符串,返回对象 + */ + public static function parse(string $str): Cookie + { + $parts = [ + 'expires' => 0, + 'path' => '/', + 'domain' => '', + 'secure' => false, + 'httponly' => false, + 'samesite' => '', + ]; + foreach (explode(';', $str) as $part) { + if (! str_contains($part, '=')) { + $key = $part; + $value = true; + } else { + [$key, $value] = explode('=', trim($part), 2); + $value = trim($value); + } + switch ($key = trim(strtolower($key))) { + case 'max-age': + $parts['expires'] = time() + (int) $value; + break; + default: + if (array_key_exists($key, $parts)) { + $parts[$key] = $value; + } else { + $parts['name'] = $key; + $parts['value'] = $value; + } + } + } + return new static( + $parts['name'], $parts['value'], + (int) $parts['expires'], $parts['path'], + $parts['domain'], (bool) $parts['secure'], + (bool) $parts['httponly'], $parts['samesite'] + ); + } + + /** + * 生成对应的Cookie字符串 + */ public function __toString(): string { $str = $this->name . '='; @@ -104,45 +149,6 @@ public function getMaxAge(): int return $this->expires !== 0 ? $this->expires - time() : 0; } - public static function parse(string $str): Cookie - { - $parts = [ - 'expires' => 0, - 'path' => '/', - 'domain' => '', - 'secure' => false, - 'httponly' => false, - 'samesite' => '', - ]; - foreach (explode(';', $str) as $part) { - if (! str_contains($part, '=')) { - $key = $part; - $value = true; - } else { - [$key, $value] = explode('=', trim($part), 2); - $value = trim($value); - } - switch ($key = trim(strtolower($key))) { - case 'max-age': - $parts['expires'] = time() + (int) $value; - break; - default: - if (array_key_exists($key, $parts)) { - $parts[$key] = $value; - } else { - $parts['name'] = $key; - $parts['value'] = $value; - } - } - } - return new static( - $parts['name'], $parts['value'], - (int) $parts['expires'], $parts['path'], - $parts['domain'], (bool) $parts['secure'], - (bool) $parts['httponly'], $parts['samesite'] - ); - } - public function getName(): string { return $this->name; From 26fcce5b73b5bde98b927996760372c7a92a4c28 Mon Sep 17 00:00:00 2001 From: chengyao <64066545+topyao@users.noreply.github.com> Date: Wed, 17 Aug 2022 09:35:52 +0800 Subject: [PATCH 07/11] =?UTF-8?q?Add:=20samesite=E5=B8=B8=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/http-message/src/Cookie.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/http-message/src/Cookie.php b/src/http-message/src/Cookie.php index ee7b57ce..2376f495 100644 --- a/src/http-message/src/Cookie.php +++ b/src/http-message/src/Cookie.php @@ -15,6 +15,10 @@ class Cookie { + public const SAME_SITE_LAX = 'lax'; + public const SAME_SITE_NONE = 'none'; + public const SAME_SITE_STRICT = 'strict'; + public function __construct( protected string $name, protected string $value, From 0679809a890ba57899678691b7a3a82518462865 Mon Sep 17 00:00:00 2001 From: chengyao <64066545+topyao@users.noreply.github.com> Date: Wed, 17 Aug 2022 09:36:16 +0800 Subject: [PATCH 08/11] =?UTF-8?q?Pref:=20=E4=BF=AE=E6=94=B9session?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/http-server/src/Middleware/SessionMiddleware.php | 8 ++++---- src/session/publish/session.php | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/http-server/src/Middleware/SessionMiddleware.php b/src/http-server/src/Middleware/SessionMiddleware.php index 880d6e26..17631fdb 100644 --- a/src/http-server/src/Middleware/SessionMiddleware.php +++ b/src/http-server/src/Middleware/SessionMiddleware.php @@ -26,7 +26,7 @@ class SessionMiddleware implements MiddlewareInterface /** * Cookie 过期时间【+9小时,实际1小时后过期,和时区有关】. */ - protected int $expires = 9 * 3600; + protected int $expires = 9 * 3600; /** * 会话Cookie名 */ @@ -35,15 +35,15 @@ class SessionMiddleware implements MiddlewareInterface protected string $path = '/'; protected string $domain = ''; protected bool $secure = true; - protected string $sameSite = 'lax'; + protected string $sameSite = Cookie::SAME_SITE_LAX; protected SessionHandlerInterface $handler; public function __construct(ConfigInterface $config) { $config = $config->get('session'); $handler = $config['handler']; - $options = $config['options']; - $this->handler = new $handler($options); + $config = $config['config']; + $this->handler = new $handler($config); } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface diff --git a/src/session/publish/session.php b/src/session/publish/session.php index e9c0bbdc..e9b8aef6 100644 --- a/src/session/publish/session.php +++ b/src/session/publish/session.php @@ -11,14 +11,14 @@ return [ 'handler' => 'Max\Session\Handler\FileHandler', - 'options' => [ + 'config' => [ 'path' => __DIR__ . '/../runtime/session', 'gcDivisor' => 100, 'gcProbability' => 1, 'gcMaxLifetime' => 1440, ], // 'handler' => 'Max\Session\Handler\RedisHandler', - // 'options' => [ + // 'config' => [ // 'connector' => 'Max\Redis\Connector\BaseConnector', // 'host' => '127.0.0.1', // 'port' => 6379, From e8a069bccef0556634b8025d86c996502ab350d2 Mon Sep 17 00:00:00 2001 From: chengyao <64066545+topyao@users.noreply.github.com> Date: Wed, 17 Aug 2022 09:36:32 +0800 Subject: [PATCH 09/11] =?UTF-8?q?Add=EF=BC=9Acsrf=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E4=B8=AD=E9=97=B4=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Middleware/VerifyCSRFToken.php | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 src/http-server/src/Middleware/VerifyCSRFToken.php diff --git a/src/http-server/src/Middleware/VerifyCSRFToken.php b/src/http-server/src/Middleware/VerifyCSRFToken.php new file mode 100644 index 00000000..051e4b26 --- /dev/null +++ b/src/http-server/src/Middleware/VerifyCSRFToken.php @@ -0,0 +1,114 @@ +shouldVerify($request)) { + if (is_null($previousToken = $request->getCookieParams()['X-XSRF-TOKEN'] ?? null)) { + $this->abort(); + } + + $token = $this->parseToken($request); + + if ('' === $token || $token !== $previousToken) { + $this->abort(); + } + } + + return $this->addCookieToResponse($handler->handle($request)); + } + + /** + * 从头部获取CSRF/XSRF Token,如果都不存在则获取表单提交的参数为__token的值 + */ + protected function parseToken(ServerRequestInterface $request): string + { + return $request->getHeaderLine('X-CSRF-TOKEN') ?: $request->getHeaderLine('X-XSRF-TOKEN') ?: ($request->getParsedBody()['__token'] ?? ''); + } + + /** + * 将token添加到cookie中 + * + * @throws Exception + */ + protected function addCookieToResponse(ResponseInterface $response): ResponseInterface + { + return $response->withCookie('X-XSRF-TOKEN', $this->newCSRFToken(), time() + $this->expires); + } + + /** + * 生成CSRF Token + * + * @throws Exception + */ + protected function newCSRFToken(): string + { + return bin2hex((random_bytes(32))); + } + + /** + * @throws CSRFException + */ + protected function abort() + { + throw new CSRFException('CSRF token is invalid', 419); + } + + /** + * 是否需要验证 + */ + protected function shouldVerify(ServerRequestInterface $request): bool + { + if (in_array($request->getMethod(), $this->shouldVerifyMethods)) { + return !collect($this->except)->first(function($pattern) use ($request) { + return $request->is($pattern); + }); + } + return false; + } +} From 31361985134f42e9f905ccc4ada0d9df321e1708 Mon Sep 17 00:00:00 2001 From: chengyao <64066545+topyao@users.noreply.github.com> Date: Wed, 17 Aug 2022 09:36:43 +0800 Subject: [PATCH 10/11] =?UTF-8?q?Add=EF=BC=9Acsrf=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E4=B8=AD=E9=97=B4=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Exception/CSRFException.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/http-server/src/Exception/CSRFException.php diff --git a/src/http-server/src/Exception/CSRFException.php b/src/http-server/src/Exception/CSRFException.php new file mode 100644 index 00000000..35604cb2 --- /dev/null +++ b/src/http-server/src/Exception/CSRFException.php @@ -0,0 +1,18 @@ + Date: Wed, 17 Aug 2022 09:37:02 +0800 Subject: [PATCH 11/11] =?UTF-8?q?Pref:=20=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Exception/Handler/WhoopsExceptionHandler.php | 6 ++++-- .../src/Middleware/ExceptionHandleMiddleware.php | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/framework/src/Exception/Handler/WhoopsExceptionHandler.php b/src/framework/src/Exception/Handler/WhoopsExceptionHandler.php index dd18e6af..326b7e1f 100644 --- a/src/framework/src/Exception/Handler/WhoopsExceptionHandler.php +++ b/src/framework/src/Exception/Handler/WhoopsExceptionHandler.php @@ -11,6 +11,8 @@ namespace Max\Exception\Handler; +use Max\Http\Message\Contract\HeaderInterface; +use Max\Http\Message\Contract\StatusCodeInterface; use Max\Http\Message\Response; use Max\Http\Message\Stream\StringStream; use Max\Utils\Str; @@ -43,12 +45,12 @@ public function handle(Throwable $throwable, ServerRequestInterface $request): ? $whoops->{RunInterface::EXCEPTION_HANDLER}($throwable); $content = ob_get_clean(); - return new Response(500, ['Content-Type' => $contentType], new StringStream($content)); + return new Response(StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR, [HeaderInterface::HEADER_CONTENT_TYPE => $contentType], new StringStream($content)); } protected function negotiateHandler(ServerRequestInterface $request) { - $accepts = $request->getHeaderLine('accept'); + $accepts = $request->getHeaderLine(HeaderInterface::HEADER_ACCEPT); foreach (self::$preference as $contentType => $handler) { if (Str::contains($accepts, $contentType)) { return [$this->setupHandler(new $handler(), $request), $contentType]; diff --git a/src/http-server/src/Middleware/ExceptionHandleMiddleware.php b/src/http-server/src/Middleware/ExceptionHandleMiddleware.php index b3423a44..6b4a6773 100644 --- a/src/http-server/src/Middleware/ExceptionHandleMiddleware.php +++ b/src/http-server/src/Middleware/ExceptionHandleMiddleware.php @@ -12,6 +12,7 @@ namespace Max\Http\Server\Middleware; use Max\Http\Message\Contract\HeaderInterface; +use Max\Http\Message\Contract\StatusCodeInterface; use Max\Http\Message\Exception\HttpException; use Max\Http\Message\Response; use Psr\Http\Message\ResponseInterface; @@ -69,6 +70,6 @@ protected function renderException(Throwable $throwable, ServerRequestInterface protected function getStatusCode(Throwable $throwable) { - return $throwable instanceof HttpException ? $throwable->getCode() : 500; + return $throwable instanceof HttpException ? $throwable->getCode() : StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR; } }