Home  >  Article  >  PHP Framework  >  Detailed explanation of how to use ETag and conditional headers for caching

Detailed explanation of how to use ETag and conditional headers for caching

藏色散人
藏色散人forward
2021-07-14 15:09:242585browse

Laravel API Performance Optimization: Caching with ETag and Conditional Headers

When writing an application with separate front-end and back-end, you have to start thinking about what kind of requests the front-end client will submit to the API to get data from the back-end again, even if you just want to verify whether the front-end cache can Real-time updates of added data. Based on the above requirements, you can use the ETag header and conditional requests.

In this blog post, I will briefly outline what ETag, If-None-Match and If-Match are to do etc., and then take a look at how I apply these to our package, which can quickly implement it into your application.

What is it

Let’s start with the meat and potatoes of it all, the ETag header. This header is a value that represents the response body in the exact state it is in. In many cases the value of ETag will be the hash value of the content, as this is the easiest way to generate and guarantee a unique identifier for the response data.

In order to ensure that the ETag header is available, we must use conditional requests. The first condition we need to set is the If-None-Match header, which is a request header for GET. After the backend receives this header, it needs to be compared with the current content. If the values ​​match, only the 304 status code will be returned. Compared with obtaining the entity resource, the response result data itself is very small. All this is very simple to implement: if your first GET request to obtain a resource returns an ETag data, your browser will automatically The request configuration If-None-Match header.

This means that if your backend simply implements etag and if-none-match, you can reduce the data transferred from your API to the frontend quantity.

The second request condition uses the if-match header. This is used to prevent mid-air collisions. In layman's terms, if we want to update data in the backend, but our frontend data is out of date, the update of the backend should be terminated, and there should be a reminder on the frontend. This is similar to how if-none-match works. After obtaining the resource containing the ETag value, you can submit a PATCH request and set a equal to the ETag value you previously received. If-Match value. The backend will then check whether the etag value of the resource currently available on the server matches the resource you sent. If there is a match, your update will be allowed. If there is no match, the 412 status code will be returned to let the front end know that the conditions do not match.

How to use

If you want to use this conditional request plug-in package in the laravel project, you can use the following command to install it:

$ composer require werk365/etagconditionals

Then add etag middleware to your route and you can use it. If you want to study how middleware works, or if you want to implement this function without using our plug-in package, please read on!

SetEtag Middleware

As you may have guessed, we can easily implement this function through middleware. Laravel actually provides us with a SetCacheHeaders middleware to set the ETag header, but it does not support HEAD requests. SetEtag The content of the middleware looks like this:

    public function handle(Request $request, Closure $next)
    {
        // Handle request
        $method = $request->getMethod();

        // Support using HEAD method for checking If-None-Match
        if ($request->isMethod('HEAD')) {
            $request->setMethod('GET');
        }

        //Handle response
        $response = $next($request);

        // Setting etag
        $etag = md5($response->getContent());
        $response->setEtag($etag);

        $request->setMethod($method);

        return $response;
    }

The first thing we need to do is get the requested method, in case we want to modify it. Then, when we are processing the HEAD request, we modify it to a GET request to ensure that the requested content has been loaded and can be encrypted. After this, we jump to the response body content that has been encrypted using the md5() method. Before returning the response, we will use the encrypted hash value as the ETag header and set the original request method back.

IfNoneMatch Middleware

This is another relatively simple method. Let's take a look at the code first:

    public function handle(Request $request, Closure $next)
    {
        // Handle request
        $method = $request->getMethod();

        // Support using HEAD method for checking If-None-Match
        if ($request->isMethod('HEAD')) {
            $request->setMethod('GET');
        }

        //Handle response
        $response = $next($request);

        $etag = '"'.md5($response->getContent()).'"';
        $noneMatch = $request->getETags();

        if (in_array($etag, $noneMatch)) {
            $response->setNotModified();
        }

        $request->setMethod($method);

        return $response;
    }

这个开头与  SetEtag 中间件相似,将确保我们可以再次处理 HEAD 请求,并根据响应内容生成 hash值。注意这种情况下我们需要将hash值用双引号包裹。双引号包裹 ETag头,然后在setEtag中间件中 setEtag()方法自动包裹hash。有了hash值后,我们可以轻松的与 If-None-Match头进行比较。由于该头可以自动加载无限个hash,并且 getETags()方法会将它们以数组形式返回,所以我们可以核对新生成的值是否存在于数组中。如果确实有匹配,我们可以在响应中使用 setNotModified()设置 304 的状态码。

IfMatch 中间件

处理 If-Match 将稍微复杂一些。这个问题归结于:我们需要找到一种方法,用来获取应该更新的当前版本的内容。这可以用多种方式实现。

  • 你可以使用 HTTP 客户端对相同资源从外部发起 GET 请求
  • 你可以查看当前请求将执行的操作, 而不是调用与之等价的 GET 请求( 例如,调用控制器上的 show() 方法)
  • 或者你可以通过内部发起一个新的 GET 请求。

在构建这个中间件时,我开始尝试使用第二个选项。出于某种原因,这对我来说似乎是最好的选择。我成功地创建了一个完全可以工作的版本,但我对结果并不满意。为了让它工作,我需要做一些假设,预设一些限制,并做了太多的工作,而我只需要创建一个新的请求就可以了。

当我们要发起一个新的请求来获取当前版本资源的时候,代码是下面这样的:

    public function handle(Request $request, Closure $next)
    {
        // 只有请求方式是 PATCH 并且已经设置了 If-Match 头
        if (! ($request->isMethod('PATCH') && $request->hasHeader('If-Match'))) {
            return $next($request);
        }

        // 对同一个点创建新的 GET 请求,
        // 复制和添加请求头,让中间件能忽略本次请求
        $getRequest = Request::create($request->getRequestUri(), 'GET');
        $getRequest->headers = $request->headers;
        $getRequest->headers->set('X-From-Middleware', 'IfMatch');
        $getResponse = app()->handle($getRequest);

        // Get content from response object and get hashes from content and etag
        $getContent = $getResponse->getContent();
        $getEtag = '"'.md5($getContent).'"';
        $ifMatch = $request->header('If-Match');

        // 比较当前和请求携带的 hash 值
        if ($getEtag !== $ifMatch) {
            return response(null, 412);
        }

        return $next($request);

所有这些中间件都将在请求生命周期开始时运行。首先,我们将过滤掉任何非 PATCH 请求或请求头中没有 If Match 的请求。之后,我们将向同一个端点发出一个新的 GET 请求,并从原来的请求中复制请求头,以便新请求可以通过身份验证中间件和其他约束。

使用这个新请求的响应,我们将再次生成一个哈希,以便与发送的哈希进行比较。如果哈希匹配,请求将被中间件允许通过。如果不匹配,将返回状态代码为 412 的请求响应。

通过使用这三个中间件,你可以在你的 Laravel 应用程序中轻松处理 ETag 和条件请求。

软件包:https://github.com/365Werk/etagconditionals

原文地址:https://hergen.nl/caching-your-laravel-api-with-etag-and-conditional-requests

译文地址:https://learnku.com/laravel/t/55539

The above is the detailed content of Detailed explanation of how to use ETag and conditional headers for caching. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:learnku.com. If there is any infringement, please contact admin@php.cn delete