hyperf/docs/en/middleware/middleware.md
2023-09-05 19:11:22 +08:00

10 KiB
Raw Blame History

Middleware

The middleware here refers to the middleware mode, which is a main function in the hyperf/http-server component. It is mainly used to weave the entire process from Request to Response. Based on PSR-15 implementation.

Principle

The middleware is mainly used to weave the entire process from Request to Response. Through the organization of multiple middleware, the flow of data is carried out in the way we order. The essence of middleware is an Onion model. Explain it through a diagram:

middleware

The order in the figure is organized in the order of Middleware 1 -> Middleware 2 -> Middleware 3. We can notice that when the middle horizontal line passes through the kernel, ie Middleware 3, it returns to Middleware 2 , this is a nested model, then the actual order is actually: Request -> Middleware 1 -> Middleware 2 -> Middleware 3 -> Middleware 2 -> Middleware 1 -> Response The focus is on the kernal, ie Middleware 3, which is the dividing point of the onion. The part before the demarcation point is actually processed based on the Request, and when the demarcation point is passed, the kernel generated the Response object, it is also the main code target of the kernel. After that, is handled the Response object by the rest of middlewares. The kernel is usually implemented by the framework, and the rest is up to you.

Define global middleware

The global middleware can ONLY be configured through the configuration file. The configuration file is located in config/autoload/middlewares.php and the configuration is as follows:

<?php
return [
    // `http` corresponds to the value corresponding to the name attribute of each server in config/autoload/server.php. This configuration is only applied to the server you configured.
    'http' => [
        // Configure your global middleware in an array, in order according to the order of the array
        YourMiddleware::class
    ],
];

Simply configure your global middleware in the file and the corresponding Server Name, it means all requests under the Server will apply the configured global middleware.

Define local middleware

When some of our middleware is only for certain requests or controllers, we can define them as local middleware, which can be defined by configuration file or defined by annotation.

Defined by configuration file

When defining a route using a configuration file, it is recommended to define the corresponding middleware through the configuration file. The configuration of the local middleware will be completed on the routing configuration.
The last parameter $options of each method defining the route of the Hyperf\HttpServer\Router\Router class will receive an array, which can be defined by passing the key value middleware and an array value to define the middleware of the route. We demonstrate this through several route definitions:

<?php
use App\Middleware\FooMiddleware;
use Hyperf\HttpServer\Router\Router;

// Each route definition method can accept a $options parameter
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]]);

// All routings under the group will apply the configured middleware
Router::addGroup(
    '/v2', function () {
        Router::get('/index', [\App\Controller\IndexController::class, 'index']);
    },
    ['middleware' => [ForMiddleware::class]]
);

Defined by annotation

When defining routes through annotations, we recommend defining middleware by means of annotations. There are two annotations for the definition of middleware, namely:

  • #[Middleware] annotation are used when defining a single middleware. Only one annotation can be defined in one place, and cannot be defined repeatedly.
  • #[Middlewares] annotation are used when defining multiple middleware. Only one annotation can be defined in one place, and then multiple middleware definitions can be implemented by defining multiple #[Middleware] annotations within the annotation.

Use #[Middleware] should use Hyperf\HttpServer\Annotation\Middleware; namespace
Use #[Middlewares] should use Hyperf\HttpServer\Annotation\Middlewares; namespace

Define a single middleware

<?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.';
    }
}

Define multiple middlewares

<?php

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([FooMiddleware::class, BarMiddleware::class])]
class IndexController
{
    public function index()
    {
        return 'Hello Hyperf.';
    }
}

Define method level middleware

It is very simple to define the method level when configuring the middleware through the configuration file. How about defined by annotations? You only need to define the annotation directly on the method. The method level middleware takes precedence over the class level middleware. Let's take a look at the code:

<?php

use App\Middleware\BarMiddleware;
use App\Middleware\FooMiddleware;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Annotation\Middleware;
use Hyperf\HttpServer\Annotation\Middlewares;

#[AutoController]
#[Middleware(FooMiddleware::class)]
class IndexController
{
    
    #[Middleware(BarMiddleware::class)]
    public function index()
    {
        return 'Hello Hyperf.';
    }
}

Generate a middleware by command:

php ./bin/hyperf.php gen:middleware Auth/FooMiddleware
<?php

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
    {
        // According to the specific business judgment logic, it is assumed that the token carried by the user is valid here.
        $isValidToken = true;
        if ($isValidToken) {
            return $handler->handle($request);
        }

        return $this->response->json(
            [
                'code' => -1,
                'data' => [
                    'error' => 'The token is invalid, preventing further execution.',
                ],
            ]
        );
    }
}

The order of execution of the middleware is FooMiddleware -> BarMiddleware.

The order of Middleware execution

We can see from the above that there are a total of 3 levels of middleware, namely global middleware, class level middleware, method level middleware. If these middlewares are defined, the order of execution is :Global Middleware -> Method Level Middleware -> Class Level Middleware.

In version >=3.0.34, a new priority configuration has been added, which allows you to change the execution order of the middleware when configuring methods and routing middleware, the higher the priority, the higher the execution order.

// middleware.php
return [
    'http' => [
        YourMiddleware::class,
        YourMiddlewareB::class => 3,
    ],
];
Router::addGroup(
    '/v2', function () {
        Router::get('/index', [\App\Controller\IndexController::class, 'index']);
    },
    [
        'middleware' => [
            FooMiddleware::class,
            FooMiddlewareB::class => 3,
        ]
    ]
);
#[AutoController]
#[Middleware(FooMiddleware::class)]
#[Middleware(FooMiddlewareB::class, 3)]
#[Middlewares([FooMiddlewareC::class => 1, BarMiddlewareD::class => 4])]
class IndexController
{
    
}

Change request and response objects globally

First, there is a storage of the most primitive PSR-7 request object and response object within the context of the coroutine, and the immutable required by the PSR-7 for the related object means The $response we called by calling $response = $response->with***() is not a rewrite of the original object, but a new object from Clone, which means the request object and response object which stored in the context of the coroutine will not change, then when we have some logic in the middleware changed the request object or response object, and we hope for the follow-up * Non-transitive * code to get the changed request object or response object, then we can set the new object to the context after changing the object, as shown in the code:

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

// $request and $response are the modified objects
$request = \Hyperf\Context\Context::set(ServerRequestInterface::class, $request);
$response = \Hyperf\Context\Context::set(ResponseInterface::class, $response);