文档地址:Hyperf
中间件的原理就是请求时拦截并进行处理,再传到对应的回调,处理返回数据。
流程:request->middleware->response。
中间件在路由定义时配置。所以根据上一篇文章hyperf 学习 二、路由_lsswear的博客-CSDN博客
中注释路由Controller、AutoController可以配置
options。
后来查了下代码,配置路由文件时,hyperf重写的addRoute中配置options也可以配置中间件。
public function addRoute($httpMethod, string $route, $handler, array $options = [])
{
$route = $this->currentGroupPrefix . $route;
$routeDatas = $this->routeParser->parse($route);
$options = $this->mergeOptions($this->currentGroupOptions, $options);
foreach ((array) $httpMethod as $method) {
$method = strtoupper($method);
foreach ($routeDatas as $routeData) {
$this->dataGenerator->addRoute($method, $routeData, new Handler($handler, $route, $options));
}
MiddlewareManager::addMiddlewares($this->server, $route, $method, $options['middleware'] ?? []);
}
}
配置文件\config\autoload\middlewares.php
return [
'http' => [
],
];
http是server名字,server之后再研究。
这表明不管是路由还是中间件都是根据server分组的。
'servers' => [
[
'name' => 'http',
'type' => Server::SERVER_HTTP,
'host' => '0.0.0.0',
'port' => 9501,
'sock_type' => SWOOLE_SOCK_TCP,
'callbacks' => [
Event::ON_REQUEST => [Hyperf\HttpServer\Server::class, 'onRequest'],
],
],
],
server中可以配置类型比如http、socket、redis等。
use App\Middleware\FooMiddleware;
use Hyperf\HttpServer\Router\Router;
// 每个路由定义方法都可接收一个 $options 参数
Router::get('/', [\App\Controller\IndexController::class, 'index'], ['middleware' => [FooMiddleware::class]]);
Router::post('/', [\App\Controller\IndexController::class, 'index'], ['middleware' => [FooMiddleware::class]]);
Router::put('/', [\App\Controller\IndexController::class, 'index'], ['middleware' => [FooMiddleware::class]]);
Router::patch('/', [\App\Controller\IndexController::class, 'index'], ['middleware' => [FooMiddleware::class]]);
Router::delete('/', [\App\Controller\IndexController::class, 'index'], ['middleware' => [FooMiddleware::class]]);
Router::head('/', [\App\Controller\IndexController::class, 'index'], ['middleware' => [FooMiddleware::class]]);
Router::addRoute(['GET', 'POST', 'HEAD'], '/index', [\App\Controller\IndexController::class, 'index'], ['middleware' => [FooMiddleware::class]]);
// 该 Group 下的所有路由都将应用配置的中间件
Router::addGroup(
'/v2', function () {
Router::get('/index', [\App\Controller\IndexController::class, 'index']);
},
['middleware' => [FooMiddleware::class]]
);
注释 | 说明 | 对应类 |
@Middleware |
定义单个中间件时使用,在一个地方仅可定义一个该注解,不可重复定义 | use Hyperf\HttpServer\Annotation\Middleware; |
@Middlewares |
定义多个中间件时使用,在一个地方仅可定义一个该注解,然后通过在该注解内定义多个 @Middleware 注解实现多个中间件的定义 |
use Hyperf\HttpServer\Annotation\Middlewares; |
namespace App\Controller;
use App\Middleware\FooMiddleware;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Annotation\Middleware;
/**
* @AutoController()
* @Middleware(FooMiddleware::class)
*/
class IndexController
{
public function index()
{
return 'Hello Hyperf.';
}
}
namespace App\Controller;
use App\Middleware\BarMiddleware;
use App\Middleware\FooMiddleware;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Annotation\Middleware;
use Hyperf\HttpServer\Annotation\Middlewares;
/**
* @AutoController()
* @Middlewares({
* @Middleware(FooMiddleware::class)
* })
*/
class IndexController
{
/**
* @Middlewares({
* @Middleware(BarMiddleware::class)
* })
*/
public function index()
{
return 'Hello Hyperf.';
}
}
先说明下hyperf是怎么调用自定义中间件。首先中间件调用是框架实现,实现可以是自定义,那么调用的入口肯定都是固定的,经过查询代码也确实如此。
自定义中间件入口函数process。
调用位置:Hyperf\Dispatcher\AbstractRequestHandler::handleRequest(),
调用顺序:
Hyperf\HttpServer\Server::__construct()注入Hyperf\Dispatcher\HttpDispatcher\HttpDispatcher;
HttpDispatcher::dispatch()调用Hyperf\Dispatcher\HttpRequestHandler::handle();
HttpRequestHandler继承Hyperf\Dispatcher\AbstractRequestHandler并使用handle()调用handleRequest()。
Hyperf\HttpServer\Server调用是通过server.php设置,配置文件通过ConfigProvider:: __invoke()加载,hyperf每个模块都有ConfigProvider。__invoke()作用使类当作方法使用。根据ConfigProvider文件内容应该是和命令行有关。
查了会代码,没查明白/(ㄒoㄒ)/~~ 就看出模块里是config容器获取再获取对应的config,没看懂容器怎么加载和定义的。
#命令行
php ./bin/hyperf.php gen:middleware Auth/FooMiddleware
#对应类代码
declare(strict_types=1);
namespace App\Middleware\Auth;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface as HttpResponse;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class FooMiddleware implements MiddlewareInterface
{
/**
* @var ContainerInterface
*/
protected $container;
/**
* @var RequestInterface
*/
protected $request;
/**
* @var HttpResponse
*/
protected $response;
public function __construct(ContainerInterface $container, HttpResponse $response, RequestInterface $request)
{
$this->container = $container;
$this->response = $response;
$this->request = $request;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
// 根据具体业务判断逻辑走向,这里假设用户携带的token有效
$isValidToken = true;
if ($isValidToken) {
return $handler->handle($request);
}
return $this->response->json(
[
'code' => -1,
'data' => [
'error' => '中间件验证token无效,阻止继续向下执行',
],
]
);
}
}
执行顺序为:全局中间件 -> 类级别中间件 -> 方法级别中间件
这块官网文档提到PSR-7,PSR-7为用php实现的http消息接口协议。
hyperf/http-server用的psr/http-message,composer地址:psr/http-message - Packagist。
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
// $request 和 $response 为修改后的对象
$request = \Hyperf\Utils\Context::set(ServerRequestInterface::class, $request);
$response = \Hyperf\Utils\Context::set(ResponseInterface::class, $response);
psr-7请求内容对象不可变,hyperf实现后设置为可变。
根据代码从server开始创建CoreMiddleware,到AbstractRequestHandler在中间件找不到时调用CoreMiddleware::process(),CoreMiddleware::process根据$dispatched状态处理。
CoreMiddleware::dispatch()由server直接调用。
所以CoreMiddleware调用顺序为先是路径找不到或请求方法不允许再是中间件不存在。
#Hyperf\HttpServer\Server
public function initCoreMiddleware(string $serverName): void
{
$this->serverName = $serverName;
$this->coreMiddleware = $this->createCoreMiddleware();
$this->routerDispatcher = $this->createDispatcher($serverName);
$config = $this->container->get(ConfigInterface::class);
$this->middlewares = $config->get('middlewares.' . $serverName, []);
$this->exceptionHandlers = $config->get('exceptions.handler.' . $serverName, $this->getDefaultExceptionHandler());
}
public function onRequest($request, $response): void
{
try {
CoordinatorManager::until(Constants::WORKER_START)->yield();
[$psr7Request, $psr7Response] = $this->initRequestAndResponse($request, $response);
$psr7Request = $this->coreMiddleware->dispatch($psr7Request);
/** @var Dispatched $dispatched */
$dispatched = $psr7Request->getAttribute(Dispatched::class);
$middlewares = $this->middlewares;
if ($dispatched->isFound()) {
$registeredMiddlewares = MiddlewareManager::get($this->serverName, $dispatched->handler->route, $psr7Request->getMethod());
$middlewares = array_merge($middlewares, $registeredMiddlewares);
}
$psr7Response = $this->dispatcher->dispatch($psr7Request, $middlewares, $this->coreMiddleware);
} catch (Throwable $throwable) {
// Delegate the exception to exception handler.
$psr7Response = $this->exceptionHandlerDispatcher->dispatch($throwable, $this->exceptionHandlers);
} finally {
// Send the Response to client.
if (! isset($psr7Response)) {
return;
}
if (isset($psr7Request) && $psr7Request->getMethod() === 'HEAD') {
$this->responseEmitter->emit($psr7Response, $response, false);
} else {
$this->responseEmitter->emit($psr7Response, $response, true);
}
}
}
#Hyperf\Dispatcher\HttpDispatcher
public function dispatch(...$params): ResponseInterface
{
/**
* @param RequestInterface $request
* @param array $middlewares
* @param MiddlewareInterface $coreHandler
*/
[$request, $middlewares, $coreHandler] = $params;
$requestHandler = new HttpRequestHandler($middlewares, $coreHandler, $this->container);
return $requestHandler->handle($request);
}
#Hyperf\Dispatcher\HttpRequestHandler
class HttpRequestHandler extends AbstractRequestHandler implements RequestHandlerInterface
{
/**
* Handles a request and produces a response.
* May call other collaborating code to generate the response.
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
return $this->handleRequest($request);
}
}
#Hyperf\Dispatcher\AbstractRequestHandler
protected function handleRequest($request)
{
if (! isset($this->middlewares[$this->offset]) && ! empty($this->coreHandler)) {
$handler = $this->coreHandler;
} else {
$handler = $this->middlewares[$this->offset];
is_string($handler) && $handler = $this->container->get($handler);
}
if (! method_exists($handler, 'process')) {
throw new InvalidArgumentException(sprintf('Invalid middleware, it has to provide a process() method.'));
}
return $handler->process($request, $this->next());
}
#Hyperf\HttpServer\CoreMiddleware
public function dispatch(ServerRequestInterface $request): ServerRequestInterface
{
$routes = $this->dispatcher->dispatch($request->getMethod(), $request->getUri()->getPath());
$dispatched = new Dispatched($routes);
return Context::set(ServerRequestInterface::class, $request->withAttribute(Dispatched::class, $dispatched));
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$request = Context::set(ServerRequestInterface::class, $request);
/** @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));
}
$response = null;
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');
}
#FastRoute\Dispatcher\RegexBasedAbstract
abstract class RegexBasedAbstract implements Dispatcher
{
/** @var mixed[][] */
protected $staticRouteMap = [];
/** @var mixed[] */
protected $variableRouteData = [];
/**
* @return mixed[]
*/
abstract protected function dispatchVariableRoute($routeData, $uri);
public function dispatch($httpMethod, $uri)
{
if (isset($this->staticRouteMap[$httpMethod][$uri])) {
$handler = $this->staticRouteMap[$httpMethod][$uri];
return [self::FOUND, $handler, []];
}
$varRouteData = $this->variableRouteData;
if (isset($varRouteData[$httpMethod])) {
$result = $this->dispatchVariableRoute($varRouteData[$httpMethod], $uri);
if ($result[0] === self::FOUND) {
return $result;
}
}
// For HEAD requests, attempt fallback to GET
if ($httpMethod === 'HEAD') {
if (isset($this->staticRouteMap['GET'][$uri])) {
$handler = $this->staticRouteMap['GET'][$uri];
return [self::FOUND, $handler, []];
}
if (isset($varRouteData['GET'])) {
$result = $this->dispatchVariableRoute($varRouteData['GET'], $uri);
if ($result[0] === self::FOUND) {
return $result;
}
}
}
// If nothing else matches, try fallback routes
if (isset($this->staticRouteMap['*'][$uri])) {
$handler = $this->staticRouteMap['*'][$uri];
return [self::FOUND, $handler, []];
}
if (isset($varRouteData['*'])) {
$result = $this->dispatchVariableRoute($varRouteData['*'], $uri);
if ($result[0] === self::FOUND) {
return $result;
}
}
// Find allowed methods for this URI by matching against all other HTTP methods as well
$allowedMethods = [];
foreach ($this->staticRouteMap as $method => $uriMap) {
if ($method !== $httpMethod && isset($uriMap[$uri])) {
$allowedMethods[] = $method;
}
}
foreach ($varRouteData as $method => $routeData) {
if ($method === $httpMethod) {
continue;
}
$result = $this->dispatchVariableRoute($routeData, $uri);
if ($result[0] === self::FOUND) {
$allowedMethods[] = $method;
}
}
// If there are no allowed methods the route simply does not exist
if ($allowedMethods) {
return [self::METHOD_NOT_ALLOWED, $allowedMethods];
}
return [self::NOT_FOUND];
}
}
#Hyperf\HttpServer\Router\Dispatched
public function __construct(array $array)
{
$this->status = $array[0];
switch ($this->status) {
case Dispatcher::METHOD_NOT_ALLOWED:
$this->params = $array[1];
break;
case Dispatcher::FOUND:
$this->handler = $array[1];
$this->params = $array[2];
break;
}
}
public function isFound(): bool
{
return $this->status === Dispatcher::FOUND;
}
自定义
declare(strict_types=1);
namespace App\Middleware;
use Hyperf\HttpMessage\Stream\SwooleStream;
use Hyperf\Utils\Contracts\Arrayable;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class CoreMiddleware extends \Hyperf\HttpServer\CoreMiddleware
{
/**
* Handle the response when cannot found any routes.
*
* @return array|Arrayable|mixed|ResponseInterface|string
*/
protected function handleNotFound(ServerRequestInterface $request)
{
// 重写路由找不到的处理逻辑
return $this->response()->withStatus(404);
}
/**
* Handle the response when the routes found but doesn't match any available methods.
*
* @return array|Arrayable|mixed|ResponseInterface|string
*/
protected function handleMethodNotAllowed(array $methods, ServerRequestInterface $request)
{
// 重写 HTTP 方法不允许的处理逻辑
return $this->response()->withStatus(405);
}
}
#定义重写
#config/autoload/dependencies.php
return [
Hyperf\HttpServer\CoreMiddleware::class => App\Middleware\CoreMiddleware::class,
];
declare(strict_types=1);
namespace App\Middleware;
use Hyperf\Utils\Context;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class CorsMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$response = Context::get(ResponseInterface::class);
$response = $response->withHeader('Access-Control-Allow-Origin', '*')
->withHeader('Access-Control-Allow-Credentials', 'true')
// Headers 可以根据实际情况进行改写。
->withHeader('Access-Control-Allow-Headers', 'DNT,Keep-Alive,User-Agent,Cache-Control,Content-Type,Authorization');
Context::set(ResponseInterface::class, $response);
if ($request->getMethod() == 'OPTIONS') {
return $response;
}
return $handler->handle($request);
}
}
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,Keep-Alive,User-Agent,Cache-Control,Content-Type,Authorization';
if ($request_method = 'OPTIONS') {
return 204;
}
}
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "POST,GET,OPTIONS"
Header always set Access-Control-Allow-Headers "Content-Type,token"
Header always set Access-Control-Max-Age 1728000
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]
跨域的话用反代理应该也能解决问题。
比如前后端分离,做前端的配置,可以反代理后端的域名。