HTTP protocol request headers If-Match, If-None-Match and ETag

Overview

In the HTTP protocol, the request headers If-Match, If-None-Match, If-Modified-Since, If-Unmodified -Since, If-Range are mainly request header standards defined to solve the problem of browser cache data. Correct judgment and use of these request headers in accordance with the protocol specifications can lead to more accurate Handle browser cache to improve system performance and reduce system bandwidth usage.

The effect of more accurate processing of web caching can be obvious:

  • 1. Reduce network interaction, speed up page response, and enhance user experience;
  • 2. Reduce network bandwidth consumption, because resources that are not updated do not need to be returned repeatedly, especially large response body requests such as pictures, videos, and downloaded files;

When the above If-xxx exists in the request, the server will judge the additional conditions. When the judgment conditions are true, standard data processing and data return will be performed. Otherwise, the corresponding HTTP error code will be returned directly. .

There are currently two types of processing rules for whether the original resources on the server have changed: based on modification time (If-Modified-Since, If-Unmodified-Since) and based on custom identification (If-Match, If-None-Match), and the other one is If-Range used to handle power-off resume transfers of files. .

Those who often do server-side development will find that time-based caching judgment cannot be very accurate. In some scenarios, the back-end resources may be changed within 1 second. The time request header is only accurate to the second, which is not enough to cover this. kind of scene. There are also some scenarios where we do not define the modification time, which may be based on whether the record is modified based on other flags. In this case, we use If-Match and If-None-Match to more accurately determine whether the resource has changed. These two headers are transmitted based on a custom string , you can define this string yourself, for example, use md5 or timestamp. It should be noted that they need to be used together with the ETag request header (ETag refers to a unique version number string, called “entity tag”).

The following describes the interaction principles and usage of If-Match, If-None-Match and ETag.

Detailed explanation

The server records an ETag (entity tag) field for the resource. When the resource is updated, the ETag will also be updated.
Therefore, if the value of the client’s If-Match is consistent with the server’s ETag, the request will be executed. Otherwise, the request will be rejected and a 412 status code will be returned.

Interaction diagram:


Sample code:

package com.example.webfluxreactivedemo1.controller;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * HTTP request header IfMatch and ETag processing
 *
 * @author Shan Hongyu
 * @date 2023/11/2 10:09
 */

@RestController
@RequestMapping
public class IfMatchController {<!-- -->

    /**
     * Get resource interface
     *
     * @param id resource ID
     * @param clientETag If-None-Match in the request header
     * @return ResponseEntity
     */
    @GetMapping("/resource/{id}")
    public ResponseEntity<String> getResource(@PathVariable String id,
                                              @RequestHeader("If-None-Match") String clientETag) {<!-- -->
        // Check whether the resource exists and whether the latest ETag of the resource matches the If-None-Match in the request header.
        boolean resourceExists = checkResourceExists(id);
        boolean etagMatch = checkETagMatch(id, clientETag);

        if (!resourceExists) {<!-- -->
            // If the resource does not exist, return 404 Not Found
            return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
        } else if (!etagMatch) {<!-- -->
            // If the resource exists and the ETag does not match (that is, the resource has changed), return the resource content
            return ResponseEntity.ok().header(HttpHeaders.ETAG, this.generateETag(id)).body("Resource content");
        } else {<!-- -->
            // If the resource exists and the ETag matches (that is, the resource has not changed), return 304 and an empty response body.
            return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
        }
    }

    /**
     * Modify article content
     *
     * @param id article ID
     * @param clientETag If-Match in the request header
     * @return ResponseEntity
     */
    @GetMapping("/updateArticle/{id}")
    public ResponseEntity<String> updateArticle(@PathVariable String id,
                                              @RequestHeader("If-Match") String clientETag) {<!-- -->
        // Check whether the resource exists and whether the latest ETag of the resource matches the If-Match in the request header.
        boolean etagMatch = checkETagMatch(id, clientETag);

        if (etagMatch) {<!-- -->
            // If the resource exists and the ETag matches, that is, the article has not been modified by others, perform the update operation.
            String newETag = "Return the latest ETag of the article";
            // articleService.update(id);
            return ResponseEntity.ok().header(HttpHeaders.ETAG, newETag).body("Modification successful");
        } else {<!-- -->
            // If the ETag does not match, it means that the article has been modified by others. The user needs to obtain the latest content and then modify and submit it based on the latest content to prevent overwriting problems caused by multiple people modifying the article content at the same time.
            //Return 412 Precondition Failed
            return ResponseEntity.status(HttpStatus.PRECONDITION_FAILED).build();
        }
    }


    // If the resource exists but the ETag does not match, return 412 Precondition Failed

    /**
     * Check if the resource exists
     *
     * @param id resource ID
     * @return true=exists
     */
    private boolean checkResourceExists(String id) {<!-- -->
        //Implement the logic here to check whether the resource exists
        return true;
    }

    /**
     * Check whether the resource ETag matches, that is, determine whether the resource's ETag has changed.
     *
     * @param id resource ID
     * @param clientETag ETag passed by the browser client
     * @return Returns false when the resource has been updated, returns true when the resource has not been updated
     */
    private boolean checkETagMatch(String id, String clientETag) {<!-- -->
        // Implement here the logic of checking whether the resource ETag matches the If-Match/If-None-Match in the request header.
        return true;
    }

    /**
     * Generate an ETag
     *
     * @param resourceId resource ID
     * @return ETag
     */
    public String generateETag(String resourceId) {<!-- -->
        //Implement the logic here to check whether the resource exists
        String eTag = "Generate etag according to your own logic based on resourceId, for example, you can use md5";

        // Note that the ETag must be returned in double quotes, which is required by the HTTP protocol specification.
        return """ + eTag + """;
    }
}

Common misunderstandings

Here are some common misconceptions about these two fields:

  • Incorrect usage: Some developers may mistakenly confuse If-None-Match and If-Match or use them upside down. For example, using If-Match when you should be using If-None-Match to check cache validity can lead to unnecessary request failures.

  • Understanding the working mechanism of Etag: Etag is a certain value associated with a specific resource, which is usually generated and stored by the server. When the resource changes, the Etag is updated accordingly. Some developers may mistakenly believe that Etag is generated and managed by the client, which may result in the failure to use If-Match or If-None-Match correctly.

  • Incorrect Etag format: The format of Etag should be an ASCII string, which may contain a "W/" prefix to indicate a weak comparison algorithm. Some developers may ignore this, causing the Etag format to be incorrect, thus affecting the effect of cache control.

In order to avoid these errors, developers are recommended to read the HTTP specification carefully to ensure that the If-Match and If-None-Match fields are correctly understood and used. At the same time, you also need to understand and master the working mechanism and correct use of Etag.

(END)