2019-04-09 01:35:32 +08:00
|
|
|
|
# 中间件
|
|
|
|
|
|
2019-07-22 12:18:33 +08:00
|
|
|
|
这里的中间件指的是"中间件模式",该功能属于 [hyperf/http-server](https://github.com/hyperf-cloud/http-server) 组件内的一项主要功能,主要用于编织从 `请求(Request)` 到 `响应(Response)` 的整个流程,该功能完成基于 [PSR-15](https://www.php-fig.org/psr/psr-15/) 实现。
|
2019-04-09 01:35:32 +08:00
|
|
|
|
|
|
|
|
|
## 原理
|
|
|
|
|
|
|
|
|
|
*中间件主要用于编织从 `请求(Request)` 到 `响应(Response)` 的整个流程*,通过对多个中间件的组织,使数据的流动按我们预定的方式进行,中间件的本质是一个 `洋葱模型`,我们通过一个图来解释它:
|
|
|
|
|
|
|
|
|
|
![middleware](./middleware.jpg)
|
|
|
|
|
|
|
|
|
|
图中的顺序为按照 `Middleware 1 -> Middleware 2 -> Middleware 3` 的顺序组织着,我们可以注意到当中间的横线穿过 `内核` 即 `Middleware 3` 后,又回到了 `Middleware 2`,为一个嵌套模型,那么实际的顺序其实就是:
|
|
|
|
|
`Request -> Middleware 1 -> Middleware 2 -> Middleware 3 -> Middleware 2 -> Middleware 1 -> Response`
|
2019-06-16 17:54:08 +08:00
|
|
|
|
重点放在 `核心` 即 `Middleware 3`,它是洋葱的分界点,分界点前面的部分其实都是基于 `请求(Request)` 进行处理,而经过了分界点时,`内核` 就产出了 `响应(Response)` 对象,也是 `内核` 的主要代码目标,在之后便是对 `响应(Response)` 进行处理了,`内核` 通常是由框架负责实现的,而其它的就由您来编排了。
|
2019-04-09 01:35:32 +08:00
|
|
|
|
|
|
|
|
|
## 定义全局中间件
|
|
|
|
|
|
|
|
|
|
全局中间件只可通过配置文件的方式来配置,配置文件位于 `config/autoload/middlewares.php` ,配置如下:
|
|
|
|
|
```php
|
|
|
|
|
<?php
|
|
|
|
|
return [
|
|
|
|
|
// http 对应 config/server.php 内每个 server 的 name 属性对应的值,该配置仅应用在该 Server 中
|
|
|
|
|
'http' => [
|
|
|
|
|
// 数组内配置您的全局中间件,顺序根据该数组的顺序
|
|
|
|
|
YourMiddleware::class
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
```
|
|
|
|
|
只需将您的全局中间件配置在该文件及对应的 `Server Name` 内,即该 `Server` 下的所有请求都会应用配置的全局中间件。
|
|
|
|
|
|
|
|
|
|
## 定义局部中间件
|
|
|
|
|
|
|
|
|
|
当我们有些中间件仅仅面向某些请求或控制器时,即可将其定义为局部中间件,可通过配置文件的方式定义或注解的方式。
|
|
|
|
|
|
|
|
|
|
### 通过配置文件定义
|
|
|
|
|
|
|
|
|
|
在使用配置文件定义路由时,推荐通过配置文件来定义对应的中间件,局部中间件的配置将在路由配置上完成。
|
|
|
|
|
`Hyperf\HttpServer\Router\Router` 类的每个定义路由的方法的最后一个参数 `$options` 都将接收一个数组,可通过传递键值 `middleware` 及一个数组值来定义该路由的中间件,我们通过几个路由定义来演示一下:
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
<?php
|
|
|
|
|
use App\Middleware\FooMiddleware;
|
|
|
|
|
use Hyperf\HttpServer\Router\Router;
|
|
|
|
|
|
|
|
|
|
// 每个路由定义方法都可接收一个 $options 参数
|
|
|
|
|
Router::get('/', [\App\Controller\IndexController::class, 'index'], ['middleware' => [ForMiddleware::class]]);
|
|
|
|
|
Router::post('/', [\App\Controller\IndexController::class, 'index'], ['middleware' => [ForMiddleware::class]]);
|
|
|
|
|
Router::put('/', [\App\Controller\IndexController::class, 'index'], ['middleware' => [ForMiddleware::class]]);
|
|
|
|
|
Router::patch('/', [\App\Controller\IndexController::class, 'index'], ['middleware' => [ForMiddleware::class]]);
|
|
|
|
|
Router::delete('/', [\App\Controller\IndexController::class, 'index'], ['middleware' => [ForMiddleware::class]]);
|
|
|
|
|
Router::head('/', [\App\Controller\IndexController::class, 'index'], ['middleware' => [ForMiddleware::class]]);
|
|
|
|
|
Router::addRoute(['GET', 'POST', 'HEAD'], '/index', [\App\Controller\IndexController::class, 'index'], ['middleware' => [ForMiddleware::class]]);
|
|
|
|
|
|
|
|
|
|
// 该 Group 下的所有路由都将应用配置的中间件
|
|
|
|
|
Router::addGroup(
|
|
|
|
|
'/v2', function () {
|
|
|
|
|
Router::get('/index', [\App\Controller\IndexController::class, 'index']);
|
|
|
|
|
},
|
|
|
|
|
['middleware' => [ForMiddleware::class]]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 通过注解定义
|
|
|
|
|
|
|
|
|
|
在通过注解定义路由时,我们推荐通过注解的方式来定义中间件,对中间件的定义有两个注解,分别为:
|
|
|
|
|
- `@Middleware` 注解为定义单个中间件时使用,在一个地方仅可定义一个该注解,不可重复定义
|
|
|
|
|
- `@Middlewares` 注解为定义多个中间件时使用,在一个地方仅可定义一个该注解,然后通过在该注解内定义多个 `@Middleware` 注解实现多个中间件的定义
|
|
|
|
|
|
2019-06-16 17:54:08 +08:00
|
|
|
|
> 使用 `@Middleware` 注解时需 `use Hyperf\HttpServer\Annotation\Middleware;` 命名空间;
|
2019-04-09 01:35:32 +08:00
|
|
|
|
> 使用 `@Middlewares` 注解时需 `use Hyperf\HttpServer\Annotation\Middlewares;` 命名空间;
|
|
|
|
|
|
|
|
|
|
定义单个中间件:
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
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.';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
定义多个中间件:
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
use App\Middleware\BarMiddleware;
|
|
|
|
|
use App\Middleware\FooMiddleware;
|
|
|
|
|
use Hyperf\HttpServer\Annotation\AutoController;
|
|
|
|
|
use Hyperf\HttpServer\Annotation\Middleware;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @AutoController()
|
|
|
|
|
* @Middlewares({
|
|
|
|
|
* @Middleware(FooMiddleware::class),
|
|
|
|
|
* @Middleware(BarMiddleware::class)
|
|
|
|
|
* })
|
|
|
|
|
*/
|
|
|
|
|
class IndexController
|
|
|
|
|
{
|
|
|
|
|
public function index()
|
|
|
|
|
{
|
|
|
|
|
return 'Hello Hyperf.';
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 00:04:48 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 定义方法级别的中间件
|
|
|
|
|
|
|
|
|
|
在通过配置文件的方式配置中间件时定义到方法级别上很简单,那么要通过注解的形式定义到方法级别呢?您只需将注解直接定义到方法上即可。
|
2019-08-26 16:46:52 +08:00
|
|
|
|
类级别上的中间件会优先于方法级别的中间件,我们通过代码来举例一下:
|
2019-04-17 00:04:48 +08:00
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
use App\Middleware\BarMiddleware;
|
|
|
|
|
use App\Middleware\FooMiddleware;
|
|
|
|
|
use Hyperf\HttpServer\Annotation\AutoController;
|
|
|
|
|
use Hyperf\HttpServer\Annotation\Middleware;
|
2019-06-16 17:54:08 +08:00
|
|
|
|
use Hyperf\HttpServer\Annotation\Middlewares;
|
2019-04-17 00:04:48 +08:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @AutoController()
|
|
|
|
|
* @Middlewares({
|
|
|
|
|
* @Middleware(FooMiddleware::class)
|
|
|
|
|
* })
|
|
|
|
|
*/
|
|
|
|
|
class IndexController
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @Middlewares({
|
|
|
|
|
* @Middleware(BarMiddleware::class)
|
|
|
|
|
* })
|
|
|
|
|
*/
|
|
|
|
|
public function index()
|
|
|
|
|
{
|
|
|
|
|
return 'Hello Hyperf.';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
2019-06-24 01:35:45 +08:00
|
|
|
|
#### 中间件相关的代码
|
2019-06-24 10:51:01 +08:00
|
|
|
|
|
|
|
|
|
生成中间件
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
php ./bin/hyperf.php gen:middleware Auth/FooMiddleware
|
|
|
|
|
```
|
2019-04-17 00:04:48 +08:00
|
|
|
|
|
2019-06-24 01:35:45 +08:00
|
|
|
|
```php
|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace App\Middleware\Auth;
|
|
|
|
|
|
2019-06-24 10:51:01 +08:00
|
|
|
|
use Hyperf\HttpServer\Contract\RequestInterface;
|
|
|
|
|
use Hyperf\HttpServer\Contract\ResponseInterface as HttpResponse;
|
2019-06-24 01:35:45 +08:00
|
|
|
|
use Psr\Container\ContainerInterface;
|
|
|
|
|
use Psr\Http\Message\ResponseInterface;
|
|
|
|
|
use Psr\Http\Message\ServerRequestInterface;
|
2019-06-24 10:51:01 +08:00
|
|
|
|
use Psr\Http\Server\MiddlewareInterface;
|
2019-06-24 01:35:45 +08:00
|
|
|
|
use Psr\Http\Server\RequestHandlerInterface;
|
|
|
|
|
|
|
|
|
|
class FooMiddleware implements MiddlewareInterface
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* @var ContainerInterface
|
|
|
|
|
*/
|
|
|
|
|
protected $container;
|
2019-06-24 10:51:01 +08:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var RequestInterface
|
|
|
|
|
*/
|
2019-06-24 01:35:45 +08:00
|
|
|
|
protected $request;
|
2019-06-24 10:51:01 +08:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var HttpResponse
|
|
|
|
|
*/
|
2019-06-24 01:35:45 +08:00
|
|
|
|
protected $response;
|
|
|
|
|
|
2019-06-24 10:51:01 +08:00
|
|
|
|
public function __construct(ContainerInterface $container, HttpResponse $response, RequestInterface $request)
|
2019-06-24 01:35:45 +08:00
|
|
|
|
{
|
|
|
|
|
$this->container = $container;
|
|
|
|
|
$this->response = $response;
|
2019-06-24 10:51:01 +08:00
|
|
|
|
$this->request = $request;
|
2019-06-24 01:35:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
|
|
|
|
{
|
2019-06-24 10:51:01 +08:00
|
|
|
|
// 根据具体业务判断逻辑走向,这里假设用户携带的token有效
|
|
|
|
|
$isValidToken = true;
|
|
|
|
|
if ($isValidToken) {
|
2019-06-24 01:35:45 +08:00
|
|
|
|
return $handler->handle($request);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-24 10:51:01 +08:00
|
|
|
|
return $this->response->json(
|
|
|
|
|
[
|
|
|
|
|
'code' => -1,
|
|
|
|
|
'data' => [
|
|
|
|
|
'error' => '中间里验证token无效,阻止继续向下执行',
|
|
|
|
|
],
|
|
|
|
|
]
|
|
|
|
|
);
|
2019-06-24 01:35:45 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
2019-04-17 00:04:48 +08:00
|
|
|
|
中间件的执行顺序为 `BarMiddleware -> FooMiddleware`。
|
|
|
|
|
|
|
|
|
|
## 中间件的执行顺序
|
|
|
|
|
|
2019-08-21 14:16:07 +08:00
|
|
|
|
我们从上面可以了解到总共有 `3` 种级别的中间件,分别为 `全局中间件`、`类级别中间件`、`方法级别中间件`,如果都定义了这些中间件,执行顺序为:`全局中间件 -> 类级别中间件 -> 方法级别中间件`。
|
2019-04-17 00:04:48 +08:00
|
|
|
|
|
2019-07-10 22:40:32 +08:00
|
|
|
|
## 全局更改请求和响应对象
|
2019-07-10 22:38:51 +08:00
|
|
|
|
|
|
|
|
|
首先,在协程上下文内是有存储最原始的 PSR-7 `请求对象` 和 `响应对象` 的,且根据 PSR-7 对相关对象所要求的 `不可变性(immutable)`,也就意味着我们在调用 `$response = $response->with***()` 所调用得到的 `$response`,并非为改写原对象,而是一个 `Clone` 出来的新对象,也就意味着我们储存在协程上下文内的 `请求对象` 和 `响应对象` 是不会改变的,那么当我们在中间件内的某些逻辑改变了 `请求对象` 或 `响应对象`,而且我们希望对后续的 *非传递性的* 代码再获取改变后的 `请求对象` 或 `响应对象`,那么我们便可以在改变对象后,将新的对象设置到上下文中,如代码所示:
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
use Psr\Http\Message\ResponseInterface;
|
|
|
|
|
use Psr\Http\Message\ServerRequestInterface;
|
|
|
|
|
|
|
|
|
|
// $request 和 $response 为修改后的对象
|
|
|
|
|
\Hyperf\Utils\Context::set(ServerRequestInterface::class, $request);
|
|
|
|
|
\Hyperf\Utils\Context::set(ResponseInterface::class, $response);
|
2019-07-18 10:33:50 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 常用中间件
|
|
|
|
|
|
|
|
|
|
### 跨域中间件
|
|
|
|
|
|
|
|
|
|
如果您需要在框架中解决跨域,则可以按照您的需求实现以下中间件
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
实际上,跨域配置也可以直接挂在 `Nginx` 上。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-25 15:31:11 +08:00
|
|
|
|
```
|