mirror of
https://gitee.com/hyperf/hyperf.git
synced 2024-11-30 10:47:44 +08:00
Merge pull request #562 from limingxinleo/1.0-merge
Merge 1.0 to master.
This commit is contained in:
commit
9cae4cdfbf
26
CHANGELOG.md
26
CHANGELOG.md
@ -45,7 +45,31 @@ return ApplicationContext::setContainer($container);
|
||||
|
||||
- [#448](https://github.com/hyperf-cloud/hyperf/pull/448) Fixed TCP Server does not works when HTTP Server or WebSocket Server exists.
|
||||
|
||||
# v1.0.15 - TBD
|
||||
# v1.0.16 - TBD
|
||||
|
||||
# v1.0.15 - 2019-09-11
|
||||
|
||||
## Fixed
|
||||
|
||||
- [#534](https://github.com/hyperf-cloud/hyperf/pull/534) Fixed Guzzle HTTP Client does not handle the response status is equal to `-3`;
|
||||
- [#541](https://github.com/hyperf-cloud/hyperf/pull/541) Fixed bug grpc client cannot be set correctly.
|
||||
- [#542](https://github.com/hyperf-cloud/hyperf/pull/542) Fixed `Hyperf\Grpc\Parser::parseResponse` returns a non-standard error code for grpc.
|
||||
- [#551](https://github.com/hyperf-cloud/hyperf/pull/551) Fixed infinite loop in grpc client when the server closed the connection.
|
||||
- [#558](https://github.com/hyperf-cloud/hyperf/pull/558) Fixed UDP Server does not works.
|
||||
|
||||
## Deleted
|
||||
|
||||
- [#545](https://github.com/hyperf-cloud/hyperf/pull/545) Deleted useless static methods `restoring` and `restored` of trait SoftDeletes.
|
||||
|
||||
## Optimized
|
||||
|
||||
- [#549](https://github.com/hyperf-cloud/hyperf/pull/549) Optimized `read` and `write` of `Hyperf\Amqp\Connection\SwooleIO`.
|
||||
- [#559](https://github.com/hyperf-cloud/hyperf/pull/559) Optimized `redirect ` of `Hyperf\HttpServer\Response`.
|
||||
- [#560](https://github.com/hyperf-cloud/hyperf/pull/560) Optimized class `Hyperf\WebSocketServer\CoreMiddleware`.
|
||||
|
||||
## Deprecated
|
||||
|
||||
- [#558](https://github.com/hyperf-cloud/hyperf/pull/558) Marked `Hyperf\Server\ServerInterface::SERVER_TCP` as deprecated, will be removed in `v1.1`.
|
||||
|
||||
# v1.0.14 - 2019-09-05
|
||||
|
||||
|
@ -101,7 +101,7 @@ return [
|
||||
'servers' => [
|
||||
[
|
||||
'name' => 'jsonrpc',
|
||||
'type' => Server::SERVER_TCP,
|
||||
'type' => Server::SERVER_BASE,
|
||||
'host' => '0.0.0.0',
|
||||
'port' => 9503,
|
||||
'sock_type' => SWOOLE_SOCK_TCP,
|
||||
|
@ -158,7 +158,6 @@ class SwooleIO extends AbstractIO
|
||||
public function read($len)
|
||||
{
|
||||
$this->check_heartbeat();
|
||||
$count = 0;
|
||||
do {
|
||||
if ($len <= strlen($this->buffer)) {
|
||||
$data = substr($this->buffer, 0, $len);
|
||||
@ -178,10 +177,7 @@ class SwooleIO extends AbstractIO
|
||||
}
|
||||
|
||||
if ($read_buffer === '') {
|
||||
if (5 < $count++) {
|
||||
throw new AMQPRuntimeException('The receiving data is empty, errno=' . $this->sock->errCode);
|
||||
}
|
||||
continue;
|
||||
throw new AMQPRuntimeException('Connection is closed.');
|
||||
}
|
||||
|
||||
$this->buffer .= $read_buffer;
|
||||
@ -192,8 +188,8 @@ class SwooleIO extends AbstractIO
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @throws AMQPRuntimeException
|
||||
* @throws \PhpAmqpLib\Exception\AMQPTimeoutException
|
||||
* @throws AMQPRuntimeException
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function write($data)
|
||||
@ -204,10 +200,6 @@ class SwooleIO extends AbstractIO
|
||||
throw new AMQPRuntimeException('Error sending data');
|
||||
}
|
||||
|
||||
if ($buffer === 0 && ! $this->sock->connected) {
|
||||
throw new AMQPRuntimeException('Broken pipe or closed connection');
|
||||
}
|
||||
|
||||
$this->lastWrite = microtime(true);
|
||||
}
|
||||
|
||||
|
@ -89,26 +89,6 @@ trait SoftDeletes
|
||||
return ! is_null($this->{$this->getDeletedAtColumn()});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a restoring model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
*/
|
||||
public static function restoring($callback)
|
||||
{
|
||||
static::registerModelEvent('restoring', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a restored model event with the dispatcher.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
*/
|
||||
public static function restored($callback)
|
||||
{
|
||||
static::registerModelEvent('restored', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model is currently force deleting.
|
||||
*
|
||||
|
@ -14,6 +14,8 @@ namespace Hyperf\GrpcClient;
|
||||
|
||||
use Google\Protobuf\Internal\Message;
|
||||
use Hyperf\Grpc\Parser;
|
||||
use Hyperf\Grpc\StatusCode;
|
||||
use Hyperf\GrpcClient\Exception\GrpcClientException;
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
use Hyperf\Utils\ChannelPool;
|
||||
use InvalidArgumentException;
|
||||
@ -36,7 +38,7 @@ class BaseClient
|
||||
if (! ($options['client'] instanceof GrpcClient)) {
|
||||
throw new InvalidArgumentException('Parameter client have to instanceof Hyperf\GrpcClient\GrpcClient');
|
||||
}
|
||||
$this->setClient($options['client']);
|
||||
$this->grpcClient = $options['client'];
|
||||
} else {
|
||||
$this->grpcClient = new GrpcClient(ApplicationContext::getContainer()->get(ChannelPool::class));
|
||||
$this->grpcClient->set($hostname, $options);
|
||||
@ -75,6 +77,7 @@ class BaseClient
|
||||
* @param array $metadata A metadata map to send to the server
|
||||
* (optional)
|
||||
* @param array $options An array of options (optional)
|
||||
* @throws GrpcClientException The client should not be used after this exception
|
||||
* @return []
|
||||
*/
|
||||
protected function simpleRequest(
|
||||
@ -83,6 +86,10 @@ class BaseClient
|
||||
$deserialize
|
||||
) {
|
||||
$streamId = $this->send($this->buildRequest($method, $argument));
|
||||
if ($streamId === 0) {
|
||||
// The client should not be used after this exception
|
||||
throw new GrpcClientException('Failed to send the request to server', StatusCode::INTERNAL);
|
||||
}
|
||||
return Parser::parseResponse($this->recv($streamId), $deserialize);
|
||||
}
|
||||
|
||||
|
17
src/grpc-client/src/Exception/GrpcClientException.php
Normal file
17
src/grpc-client/src/Exception/GrpcClientException.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GrpcClient\Exception;
|
||||
|
||||
class GrpcClientException extends \RuntimeException
|
||||
{
|
||||
}
|
@ -168,6 +168,10 @@ class GrpcClient
|
||||
$shouldKill = true;
|
||||
} else {
|
||||
$shouldKill = ! $this->getHttpClient()->connect();
|
||||
if ($shouldKill) {
|
||||
// Set `connected` of http client to `false`
|
||||
$this->getHttpClient()->close();
|
||||
}
|
||||
}
|
||||
// Clear the receive channel map
|
||||
if (! empty($this->recvChannelMap)) {
|
||||
@ -325,7 +329,7 @@ class GrpcClient
|
||||
}
|
||||
} else {
|
||||
// If no response, then close all the connection.
|
||||
if (! $this->isConnected() && $this->closeRecv()) {
|
||||
if ($this->closeRecv()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,9 @@ class Parser
|
||||
return ['No response', self::GRPC_ERROR_NO_RESPONSE, $response];
|
||||
}
|
||||
if ($response->statusCode !== 200) {
|
||||
return ['Http status Error', $response->errCode ?: $response->statusCode, $response];
|
||||
$message = $response->headers['grpc-message'] ?? 'Http status Error';
|
||||
$code = $response->headers['grpc-status'] ?? ($response->errCode ?: $response->statusCode);
|
||||
return [$message, (int) $code, $response];
|
||||
}
|
||||
$grpc_status = (int) ($response->headers['grpc-status'] ?? 0);
|
||||
if ($grpc_status !== 0) {
|
||||
|
@ -190,13 +190,18 @@ class CoroutineHandler
|
||||
'errCode' => $errCode,
|
||||
];
|
||||
|
||||
if ($statusCode === -1) {
|
||||
if ($statusCode === SWOOLE_HTTP_CLIENT_ESTATUS_CONNECT_FAILED) {
|
||||
return new ConnectException(sprintf('Connection failed, errCode=%s', $errCode), $request, null, $ctx);
|
||||
}
|
||||
if ($statusCode === -2) {
|
||||
|
||||
if ($statusCode === SWOOLE_HTTP_CLIENT_ESTATUS_REQUEST_TIMEOUT) {
|
||||
return new RequestException(sprintf('Request timed out, errCode=%s', $errCode), $request, null, null, $ctx);
|
||||
}
|
||||
|
||||
if ($statusCode === SWOOLE_HTTP_CLIENT_ESTATUS_SERVER_RESET) {
|
||||
return new RequestException('Server reset', $request, null, null, $ctx);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -147,13 +147,18 @@ class CoroutineHandler
|
||||
$statusCode = $client->statusCode;
|
||||
$errCode = $client->errCode;
|
||||
|
||||
if ($statusCode === -1) {
|
||||
if ($statusCode === SWOOLE_HTTP_CLIENT_ESTATUS_CONNECT_FAILED) {
|
||||
return new RingException(sprintf('Connection timed out errCode=%s', $errCode));
|
||||
}
|
||||
if ($statusCode === -2) {
|
||||
|
||||
if ($statusCode === SWOOLE_HTTP_CLIENT_ESTATUS_REQUEST_TIMEOUT) {
|
||||
return new RingException('Request timed out');
|
||||
}
|
||||
|
||||
if ($statusCode === SWOOLE_HTTP_CLIENT_ESTATUS_SERVER_RESET) {
|
||||
return new RingException('Server reset');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -14,12 +14,15 @@ namespace HyperfTest\Guzzle\Cases;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use Hyperf\Guzzle\CoroutineHandler;
|
||||
use HyperfTest\Guzzle\Stub\CoroutineHandlerStub;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Swoole\Coroutine\Http\Client as SwooleHttpClient;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -252,6 +255,31 @@ class CoroutineHandlerTest extends TestCase
|
||||
$this->assertEquals('Basic ' . base64_encode('username:password'), $json['headers']['Authorization']);
|
||||
}
|
||||
|
||||
public function testStatusCode()
|
||||
{
|
||||
$client = new SwooleHttpClient('127.0.0.1', 80);
|
||||
$client->statusCode = -1;
|
||||
$request = \Mockery::mock(RequestInterface::class);
|
||||
$handler = new CoroutineHandlerStub();
|
||||
$ex = $handler->checkStatusCode($client, $request);
|
||||
$this->assertInstanceOf(ConnectException::class, $ex);
|
||||
|
||||
$client = new SwooleHttpClient('127.0.0.1', 80);
|
||||
$client->statusCode = -2;
|
||||
$request = \Mockery::mock(RequestInterface::class);
|
||||
$handler = new CoroutineHandlerStub();
|
||||
$ex = $handler->checkStatusCode($client, $request);
|
||||
$this->assertInstanceOf(RequestException::class, $ex);
|
||||
|
||||
$client = new SwooleHttpClient('127.0.0.1', 80);
|
||||
$client->statusCode = -3;
|
||||
$request = \Mockery::mock(RequestInterface::class);
|
||||
$handler = new CoroutineHandlerStub();
|
||||
$ex = $handler->checkStatusCode($client, $request);
|
||||
$this->assertInstanceOf(RequestException::class, $ex);
|
||||
$this->assertSame('Server reset', $ex->getMessage());
|
||||
}
|
||||
|
||||
protected function getHandler($options = [])
|
||||
{
|
||||
return new CoroutineHandler($options);
|
||||
|
@ -12,9 +12,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace HyerfTest\Guzzle\Cases;
|
||||
|
||||
use GuzzleHttp\Ring\Exception\RingException;
|
||||
use Hyperf\Guzzle\RingPHP\CoroutineHandler;
|
||||
use HyperfTest\Guzzle\Stub\RingPHPCoroutineHanderStub;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Swoole\Coroutine\Http\Client as SwooleHttpClient;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -80,4 +83,30 @@ class RingPHPCoroutineHandlerTest extends TestCase
|
||||
|
||||
$this->assertEquals('/echo?a=1&b=2', $json['uri']);
|
||||
}
|
||||
|
||||
public function testStatusCode()
|
||||
{
|
||||
$client = new SwooleHttpClient('127.0.0.1', 80);
|
||||
$client->statusCode = -1;
|
||||
$request = \Mockery::mock(RequestInterface::class);
|
||||
$handler = new RingPHPCoroutineHanderStub();
|
||||
$ex = $handler->checkStatusCode($client, $request);
|
||||
$this->assertInstanceOf(RingException::class, $ex);
|
||||
|
||||
$client = new SwooleHttpClient('127.0.0.1', 80);
|
||||
$client->statusCode = -2;
|
||||
$request = \Mockery::mock(RequestInterface::class);
|
||||
$handler = new RingPHPCoroutineHanderStub();
|
||||
$ex = $handler->checkStatusCode($client, $request);
|
||||
$this->assertInstanceOf(RingException::class, $ex);
|
||||
$this->assertSame('Request timed out', $ex->getMessage());
|
||||
|
||||
$client = new SwooleHttpClient('127.0.0.1', 80);
|
||||
$client->statusCode = -3;
|
||||
$request = \Mockery::mock(RequestInterface::class);
|
||||
$handler = new RingPHPCoroutineHanderStub();
|
||||
$ex = $handler->checkStatusCode($client, $request);
|
||||
$this->assertInstanceOf(RingException::class, $ex);
|
||||
$this->assertSame('Server reset', $ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,11 @@ use Swoole\Coroutine\Http\Client;
|
||||
|
||||
class CoroutineHandlerStub extends CoroutineHandler
|
||||
{
|
||||
public function checkStatusCode(Client $client, $request)
|
||||
{
|
||||
return parent::checkStatusCode($client, $request);
|
||||
}
|
||||
|
||||
protected function execute(Client $client, $path)
|
||||
{
|
||||
$client->body = json_encode([
|
||||
|
@ -17,6 +17,11 @@ use Swoole\Coroutine\Http\Client;
|
||||
|
||||
class RingPHPCoroutineHanderStub extends CoroutineHandler
|
||||
{
|
||||
public function checkStatusCode($client, $request)
|
||||
{
|
||||
return parent::checkStatusCode($client, $request);
|
||||
}
|
||||
|
||||
protected function execute(Client $client, $path)
|
||||
{
|
||||
$client->body = json_encode([
|
||||
|
@ -122,7 +122,7 @@ class Response implements PsrResponseInterface, ResponseInterface
|
||||
$uri = $request->getUri();
|
||||
$host = $uri->getAuthority();
|
||||
// Build the url by $schema and host.
|
||||
return $schema . '://' . $host . '/' . $toUrl;
|
||||
return $schema . '://' . $host . (Str::startsWith($toUrl, '/') ? $toUrl : '/' . $toUrl);
|
||||
});
|
||||
return $this->getResponse()->withStatus($status)->withAddedHeader('Location', $toUrl);
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ namespace HyperfTest\HttpServer;
|
||||
|
||||
use Hyperf\HttpMessage\Cookie\Cookie;
|
||||
use Hyperf\HttpMessage\Stream\SwooleStream;
|
||||
use Hyperf\HttpMessage\Uri\Uri;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Hyperf\HttpServer\Contract\ResponseInterface;
|
||||
use Hyperf\HttpServer\Response;
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
@ -41,6 +43,9 @@ class ResponseTest extends TestCase
|
||||
public function testRedirect()
|
||||
{
|
||||
$container = Mockery::mock(ContainerInterface::class);
|
||||
$request = Mockery::mock(RequestInterface::class);
|
||||
$request->shouldReceive('getUri')->andReturn(new Uri('http://127.0.0.1:9501'));
|
||||
$container->shouldReceive('get')->with(RequestInterface::class)->andReturn($request);
|
||||
ApplicationContext::setContainer($container);
|
||||
|
||||
$psrResponse = new \Hyperf\HttpMessage\Base\Response();
|
||||
@ -57,6 +62,16 @@ class ResponseTest extends TestCase
|
||||
|
||||
$this->assertSame(302, $res->getStatusCode());
|
||||
$this->assertSame('http://www.baidu.com', $res->getHeaderLine('Location'));
|
||||
|
||||
$response = new Response();
|
||||
$res = $response->redirect('/index');
|
||||
$this->assertSame(302, $res->getStatusCode());
|
||||
$this->assertSame('http://127.0.0.1:9501/index', $res->getHeaderLine('Location'));
|
||||
|
||||
$response = new Response();
|
||||
$res = $response->redirect('index');
|
||||
$this->assertSame(302, $res->getStatusCode());
|
||||
$this->assertSame('http://127.0.0.1:9501/index', $res->getHeaderLine('Location'));
|
||||
}
|
||||
|
||||
public function testToXml()
|
||||
|
@ -18,6 +18,7 @@ use Hyperf\Event\Contract\ListenerInterface;
|
||||
use Hyperf\Framework\Event\AfterWorkerStart;
|
||||
use Hyperf\Server\Server;
|
||||
use Hyperf\Server\ServerManager;
|
||||
use Swoole\Server\Port;
|
||||
|
||||
/**
|
||||
* @Listener
|
||||
@ -52,11 +53,12 @@ class AfterWorkerStartListener implements ListenerInterface
|
||||
{
|
||||
/** @var AfterWorkerStart $event */
|
||||
if ($event->workerId === 0) {
|
||||
/** @var Port $server */
|
||||
foreach (ServerManager::list() as $name => [$type, $server]) {
|
||||
$listen = $server->host . ':' . $server->port;
|
||||
$type = value(function () use ($type) {
|
||||
switch ($type) {
|
||||
case Server::SERVER_TCP:
|
||||
case Server::SERVER_BASE:
|
||||
return 'TCP';
|
||||
break;
|
||||
case Server::SERVER_WEBSOCKET:
|
||||
|
@ -58,6 +58,7 @@ class Port
|
||||
isset($config['type']) && $port->setType($config['type']);
|
||||
isset($config['host']) && $port->setHost($config['host']);
|
||||
isset($config['port']) && $port->setPort($config['port']);
|
||||
isset($config['sock_type']) && $port->setSockType($config['sock_type']);
|
||||
isset($config['callbacks']) && $port->setCallbacks($config['callbacks']);
|
||||
isset($config['settings']) && $port->setSettings($config['settings']);
|
||||
|
||||
|
@ -172,7 +172,7 @@ class Server implements ServerInterface
|
||||
return new SwooleHttpServer($host, $port, $mode, $sockType);
|
||||
case ServerInterface::SERVER_WEBSOCKET:
|
||||
return new SwooleWebSocketServer($host, $port, $mode, $sockType);
|
||||
case ServerInterface::SERVER_TCP:
|
||||
case ServerInterface::SERVER_BASE:
|
||||
return new SwooleServer($host, $port, $mode, $sockType);
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,12 @@ interface ServerInterface
|
||||
|
||||
const SERVER_WEBSOCKET = 2;
|
||||
|
||||
const SERVER_TCP = 3;
|
||||
const SERVER_BASE = 3;
|
||||
|
||||
/**
|
||||
* @deprecated v1.1
|
||||
*/
|
||||
const SERVER_TCP = self::SERVER_BASE;
|
||||
|
||||
public function __construct(ContainerInterface $container, LoggerInterface $logger, EventDispatcherInterface $dispatcher);
|
||||
|
||||
|
@ -12,50 +12,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace Hyperf\WebSocketServer;
|
||||
|
||||
use FastRoute\Dispatcher;
|
||||
use Hyperf\HttpServer\CoreMiddleware as HttpCoreMiddleware;
|
||||
use Hyperf\HttpServer\Router\Dispatched;
|
||||
use Hyperf\Server\Exception\ServerException;
|
||||
use Hyperf\Utils\Context;
|
||||
use Hyperf\Utils\Contracts\Arrayable;
|
||||
use Hyperf\WebSocketServer\Exception\WebSocketHandeShakeException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class CoreMiddleware extends HttpCoreMiddleware
|
||||
{
|
||||
/**
|
||||
* Process an incoming server request and return a response, optionally delegating
|
||||
* response creation to a handler.
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
/** @var Dispatched $dispatched */
|
||||
$dispatched = $request->getAttribute(Dispatched::class);
|
||||
|
||||
if (! $dispatched instanceof Dispatched) {
|
||||
throw new ServerException(sprintf('The dispatched object is not a %s object.', Dispatched::class));
|
||||
}
|
||||
|
||||
switch ($dispatched->status) {
|
||||
case Dispatcher::NOT_FOUND:
|
||||
$response = $this->handleNotFound($request);
|
||||
break;
|
||||
case Dispatcher::METHOD_NOT_ALLOWED:
|
||||
$response = $this->handleMethodNotAllowed($dispatched->params, $request);
|
||||
break;
|
||||
case Dispatcher::FOUND:
|
||||
$response = $this->handleFound($dispatched, $request);
|
||||
break;
|
||||
}
|
||||
if (! $response instanceof ResponseInterface) {
|
||||
$response = $this->transferToResponse($response, $request);
|
||||
}
|
||||
|
||||
return $response->withAddedHeader('Server', 'Hyperf');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the response when found.
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user