[laravel series 3.4] Application of Middleware in routing and controller

Time:2022-5-3

Application of Middleware in routing and controller

What is middleware? In the era of traditional frameworks, there are few middleware concepts. I first came into contact with this concept when I was learning mysql. I learned that components such as MYCAT are also called middleware. Since it is in the middle, it is a thing sandwiched between application and call. Let’s take the request as an example. A request goes through three processes: receiving, processing and returning, and the middleware can be regarded as some operations sandwiched between these three operations. For example, when our request does not arrive at the route or controller, we can make some pre judgment through the middleware, such as whether the parameters are legal or not, and the judgment of login status. For example, when we use laravel for business development, the middleware we often need to write is the middleware to process login information and solve cross domain problems (larave8 has its own cross domain components).

Learn node before JS, there is also Middleware in the express framework, and the concept is exactly the same as that of laravel’s middleware. Now, this middleware technology has become one of the necessary functions of various modern frameworks. In TP3, in fact, those hook methods can also be regarded as a kind of middleware, but they are that the request has reached the controller, but some hook functions are embedded before calling the specific controller method. You can refer to the relevant knowledge of hook functions[PHP design pattern template method pattern]https://mp.weixin.qq.com/s/2sX1ASQpnMybJ2xFqRR3Ig 。

Well, let’s not go far. Let’s take a look at how middleware is used in laravel.

Middleware definition

Creating a middleware can also be done through the command line.

php artisan make:middleware MiddlewareTest

Through this command, we will find that a file named middlewaretest is created in the directory app / HTTP / middleware PHP file. This is a middleware file. Of course, you can also create it yourself. Just put the created file in this directory. At the same time, in this directory, we can also see many middleware systems have been prepared for us. We’ll learn one or two of them later, but before that, let’s take a look at the automatically generated middleware test What’s in the PHP file.

class MiddlewareTest
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        return $next($request);
    }
}

It seems a little simple. There is only one handle () method, and then there are two parameters, one is request and the other is the next parameter of closure type. I won’t say much about request. As mentioned in previous articles, this request runs through the whole laravel application, so it’s not uncommon to have it in middleware. More importantly, in fact, our middleware is mainly the intermediate operation of request and response, so this request is very important.

Besides, what the hell is next()? How is a closure type parameter? If you have studied the design pattern series articles I wrote before, you will not be unfamiliar. Think about the pattern of responsibility chain. Friends who can’t remember or haven’t seen it can move forward[responsibility chain mode of PHP design mode]https://mp.weixin.qq.com/s/ZA9vyCEkEg9_KTll-JkcqwLearn first and then come back. I believe you will understand the basic principle of middleware immediately.

OK, no fuss. This next is actually a responsibility chain formed in the framework, or a pipeline. They are slightly different, but they are basically similar in essence, that is, let the request flow down in a pipeline like water, and then reach an end point (such as a controller), and then change another pipe to flow back (that is, response). The next node is the next node to handle the request. The specific content is to refer to the explanation of the responsibility chain model, because their principles are indeed interlinked.

Instead of writing your own code, let’s take a look at what is written in the middleware provided by the framework. First, we see the function of preventing CSRF attack mentioned in the previous article, which is judged through middleware_ Whether the token tag exists.

// laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php
public function handle($request, Closure $next)
{
    if (
        $this->isReading($request) ||
        $this->runningUnitTests() ||
        $this->inExceptArray($request) ||
        $this->tokensMatch($request)
    ) {
        return tap($next($request), function ($response) use ($request) {
            if ($this->shouldAddXsrfTokenCookie()) {
                $this->addCookieToResponse($request, $response);
            }
        });
    }

    throw new TokenMismatchException('CSRF token mismatch.');
}

app/Http/Middleware/VerifyCsrfToken. PHP inherits from laravel / framework / SRC / illuminate / foundation / HTTP / Middleware / verifycsrftoken PHP, that is, the source code is at the bottom of the framework, so we go directly to laravel / framework / SRC / illuminate / foundation / HTTP / Middleware / verifycsrftoken PHP to view. There is also a handle () in it, which is the above code.

The handle () will read whether there is in the request_ Whether there is x-csrf-token information in the token parameter or header information, which will be compared with that in the session_ Compare the token information. After success, next will be called inside the if condition, that is, the following middleware or other pipeline nodes will be notified to continue the processing of the request. If it fails, the CSRF token mismatch error will be returned, and the request will be aborted. The relevant source codes are in verifycsrftoken In PHP, we won’t show them one by one here. You can check them by yourself.

We can define and analyze the default middleware we just provided. Just do some simple functions.

public function handle(Request $request, Closure $next)
{
    if($request->a){
        $request->aa = $request->a;
    }

    $response = $next($request);

    $response->setContent($response->content() . ' time:' . time());

    return $response;
}

Eh, it seems to be different from the middleware provided by default. Why don’t we directly return next (), but use a variable to connect next (), and then do some operations before returning? This is actually the function of post middleware.

In fact, as we said earlier, the front-end middleware is to process the request before next (). For example, we add a new field to the request. The post middleware can perform some operations on the response when the pipeline flows back after the end of next (). For example, we have added a time output for the response. Of course, in general, we still try our best to handle the response data at the controller, and the biggest advantage of the post middleware is that it can log the complete request and response for one request. However, these are still based on the requirements of business functions. As long as you know that there is this function, you can.

In the business development of front-end middleware, what we use most is the verification of login authentication. For example, whether the user logs in and has permission can be judged through the middleware before reaching the controller. If the user does not log in or has insufficient permission, the error information will be returned directly. Just like the middleware of CSRF, if not_ If you can’t get to the controller at all, you will directly return an error message.

Next, we need to prepare a controller.

class MiddlewareTestController extends Controller
{
    public function test(){
        $a = request()->a;
        $aa = request()->aa;
        return $a + $aa;
    }
}

The controller is very simple. We just get and add the parameters in the received request. As we saw in the middleware earlier, if there is a parameter, we will copy an AA parameter

We are ready for middleware and controller. The next step is how to use middleware. Let’s talk about it one by one.

Use middleware on Routing

Using middleware in routing is very simple. We only need a middleware method.

Route::get('middleware/test', 'App\Http\Controllers\[email protected]')->middleware(\App\Http\Middleware\MiddlewareTest::class);

Is it a little too simple? Now we specify a middleware defined by ourselves for this route. Note whether other routes not written follow this middleware. In other words, middleware is defined in the route, and only the route we specify will execute the corresponding middleware code.

Middleware is used in the controller

It is the simplest and most convenient way to configure Middleware in routing, but if we say we don’t want to configure it in routing, for example, the methods in this controller may define multiple routes. If we want all defined routes to use this middleware, in addition to the global configuration middleware to be discussed later, we can also define the middleware to be used in a controller.

// routes/web.php
Route::get('middleware/noroute/test', 'App\Http\Controllers\[email protected]');

// app/Http/Controllers
class MiddlewareTestController extends Controller
{
    public function __construct()
    {
        $this->middleware(MiddlewareTest::class);
    }

    // ……………………
    // ……………………
    // ……………………
}

In the above test code, we still use the same controller method as the above route, but on this route, we do not specify the middleware, but specify the middleware through the middleware () method in the constructor in the controller code, so that all methods in the controller can execute the specified middleware content. We define a new controller method and specify a routing test without middleware.

// routes/web.php
Route::get('middleware/noroute/test2', 'App\Http\Controllers\[email protected]');

// app/Http/Controllers
public function test2(){
    $a = request()->a;
    $aa = request()->aa;
    return $a * $aa;
}

It can be seen that the middleware also works normally for this new routing and controller method.

Global use of Middleware

The above contents are all about using middleware in a specific situation, such as a specified route or a specified controller. Laravel also prepared the definition of global middleware for us. The meaning of global is obvious. This middleware will be added to all requests.

// App\Http\Kernel.php
protected $middleware = [
    // \App\Http\Middleware\TrustHosts::class,
    \App\Http\Middleware\TrustProxies::class,
    \Fruitcake\Cors\HandleCors::class,
    \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
    \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
    \App\Http\Middleware\TrimStrings::class,
    \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    \App\Http\Middleware\MiddlewareTest::class,
];

We just need to find app \ http \ kernel PHP file, add the last line in the middleware variable, that is, the middleware we customized. In this way, all requests will go through the middleware. Kernel. PHP is a core file. If we continue to look at it, we will find that there are two variables below, one is middlewaregroups and the other is routemiddleware. In fact, as can be seen from the name, middlewaregroups are middleware groups. There are two middleware groups defined by default, namely web and API. In fact, they correspond to the API under the routing folder PHP and web The middleware to be loaded by PHP. In the source code, we can find app / providers / routeserviceprovider PHP file, check the boot () method inside.

public function boot()
{
    $this->configureRateLimiting();

    $this->routes(function () {
        Route::prefix('api')
            ->middleware('api')
            ->namespace($this->namespace)
            ->group(base_path('routes/api.php'));

        Route::middleware('web')
            ->namespace($this->namespace)
            ->group(base_path('routes/web.php'));
    });
}

In this boot () method, you can see that it defines two routes, and loads the corresponding two files in the routes directory. Then, the middleware specified by middleware () is actually the two middleware groups defined in the middleware group. Since it is a group concept, all Middleware in the group will be executed in these two routing files. You can try to comment out the middleware \ app \ http \ middleware \ verifycsrftoken:: Class under the web group, and you will find the web All requests under PHP do not need CSRF authentication.

Another routemiddleware means to give an alias to the middleware. For example, we add one to this variable:

'middlewaretest' => \App\Http\Middleware\MiddlewareTest::class,

Then, in routing, you can directly use the defined name in the middleware () method.

Route::get('middleware/test', 'App\Http\Controllers\[email protected]')->middleware('middlewaretest');

Middleware call source code analysis

The core usage of middleware is the above content. For other functions, you can refer to the official documents for learning. Next, we will enter the call analysis of middleware source code. In fact, as mentioned in the previous article and at the beginning of this article, middleware is a typical application of the responsibility chain model. In laravel, this responsibility chain is realized in the form of pipeline.

Execute the entry file public / index PHP, the first step will come to laravel / framework / SRC / illuminate / foundation / HTTP / kernel In PHP, notice the kernel PHP is not only the file in the source code, but also the core file of the whole laravel framework. In its constructor, a syncmiddlewaretorouter () method will be called.

// laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
protected function syncMiddlewareToRouter()
{
    $this->router->middlewarePriority = $this->middlewarePriority;

    foreach ($this->middlewareGroups as $key => $middleware) {
        $this->router->middlewareGroup($key, $middleware);
    }

    foreach ($this->routeMiddleware as $key => $middleware) {
        $this->router->aliasMiddleware($key, $middleware);
    }
}

As can be seen from the method name, the function of this method is to the routing synchronization middleware, which is to put us in APP / HTTP / kernel The middleware array defined in PHP is placed in the routing object laravel / framework / SRC / illuminate / routing / router PHP. At this time, the middleware has been read. Next, in index In the handle () method called in PHP, a routing pipeline will be constructed through the sendrequestthroughrouter () method.

protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);

    Facade::clearResolvedInstance('request');

    $this->bootstrap();

    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
}

Pipeline is a pipeline. In through (), we will save the default global Middleware in the pipes variable of pipeline, and then let the request flow all the way through this middleware pipeline like water.

The above is the global middleware, remember in kernel Will we pass middleware to routing objects in PHP? Next, after the route construction is completed, through the route The runroutewithinstack () method in PHP constructs the pipeline related to the routing middleware.

// laravel/framework/src/Illuminate/Routing/Router.php
protected function runRouteWithinStack(Route $route, Request $request)
{
    $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
                            $this->container->make('middleware.disable') === true;

    $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);

    return (new Pipeline($this->container))
                ->send($request)
                ->through($middleware)
                ->then(function ($request) use ($route) {
                    return $this->prepareResponse(
                        $request, $route->run()
                    );
                });
}

For the analysis of pipes, we will learn again in the articles related to the core architecture. Now, you only need to know that the water pipe has been paved. The next step is to make the request, that is, let our water flow in the pipe. Middleware is the valves in this pipeline. We can filter the water, turn off the valves to prevent the water from flowing, or let the water flow back from another pipeline. Let’s give full play to your imagination.

summary

These are the contents of middleware. In fact, these methods are enough for our daily development and application. The analysis of the source code is not too in-depth, because if we go further, we will realize the pipeline related implementation. Therefore, here we just simply point out when the middleware will be loaded and put into the pipeline. We will talk about the follow-up later. Don’t be anxious. Eating hot tofu in one bite will burn your mouth. If you still have a lot to say, you might as well debug it yourself and see how the pipeline is implemented. We will talk about the pipeline later.

Reference documents:

https://learnku.com/docs/laravel/8.x/middleware/9366#b53cb2