深入了解HTTP HEAD请求

2023-04-13

HTTP HEAD请求

HEAD请求是一种HTTP请求方法,与GET请求类似,但不返回响应正文主体,只返回响应头部信息。它通常用于获取与资源相关的元数据,如响应状态码、响应头部、最后修改时间等,而不需要传输实际的资源内容。HEAD请求经常被用来检查服务器是否可用、文件是否存在以及检查资源的更新时间戳等操作,其请求及响应报文如下所示:

请求报文:

HEAD / HTTP/1.1
Host: www.devzhi.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36
Accept: */*
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

响应报文:

HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Thu, 13 Apr 2023 02:41:00 GMT
Content-Type: text/plain
Content-Length: 18
Connection: keep-alive

如何实现HEAD接口

Spring Boot

在Spring Boot中实现HEAD请求方法跟其他HTTP请求方法的实现方式基本相同,只需要在对应的Controller中定义一个使用@RequestMapping注解的方法,并设置其RequestMethod为HEAD即可。

举个例子,假设你要对某个请求路径"/example"实现HEAD请求方法,可以在Controller类中增加如下代码:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ExampleController {

    @RequestMapping(value = "/example", method = RequestMethod.HEAD)
    public ResponseEntity<Void> headExample() {
        // 这里可以写一些业务逻辑

        // 返回一个空的ResponseEntity,表示当前请求资源存在
        return ResponseEntity.ok().build();
    }
}

在上述代码中,我们在方法headExample()上标注了@RequestMapping注解,并将其RequestMethod设置为HEAD。在方法体中,我们可以编写一些业务逻辑来处理该请求,然后通过返回一个空的ResponseEntity来表示当前请求资源存在。

需要注意的是,由于HEAD请求方法不会返回具体的内容,因此在返回ResponseEntity时,我们只需要返回状态码即可。如果当前请求的资源不存在,我们可以返回404状态码。

除此之外,在Spring Boot中实现GET接口后HEAD接口会被自动实现,Spring Boot会自动为对应的请求路径生成HEAD请求的处理逻辑。这是因为HTTP协议规定,在没有明确指定HEAD请求处理逻辑的情况下,服务器应该使用GET请求的处理逻辑来处理HEAD请求。

因此,如果你已经在Controller中实现了对应的GET请求方法,就不需要再显式地为同样的请求路径编写HEAD请求方法了。当客户端发送HEAD请求时,Spring Boot会自动调用对应的GET请求方法,并忽略其返回值,只返回请求头信息。

在 Spring Boot 中,处理 HTTP 请求的核心组件是 DispatcherServlet。当收到一个请求时,DispatcherServlet 会根据请求路径和请求方法查找对应的 RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter,并将请求交给它们进行处理。

RequestMappingHandlerMapping 负责查找匹配当前请求的 Controller 方法,而 RequestMappingHandlerAdapter 则负责调用 Controller 方法并将其返回结果转换为响应内容。在默认情况下,Spring Boot 使用 DefaultRequestMappingHandlerAdapter 和 RequestMappingHandlerMapping 来处理 HTTP 请求。

对于 HEAD 请求,DispatcherServlet 会先调用 RequestMappingHandlerMapping 的getHandler() 方法来查找对应的处理器(handler)。在这个方法中,RequestMappingHandlerMapping 会通过遍历所有已注册的 HandlerMethod,找到对应的 GET 请求的处理器方法,并使用它来处理当前 HEAD 请求。

具体而言,就是首先查找是否有对应的 GET 请求方法,然后将其包装成一个空的 ResponseBody,并设置响应头信息,最后返回这个 ResponseBody 对象。这样,客户端就能够得到与 GET 请求相同的响应头信息,但是不会获取到响应正文内容。

如果在当前路径下没有对应的 GET 请求方法,则会返回一个 404 Not Found 响应。

下面是一个简化版的代码示例,展示了 Spring Boot 如何处理 HEAD 请求:

public class RequestMappingHandlerMapping {
    public Object getHandler(HttpServletRequest request) {
        // 查找当前请求的处理器方法
        HandlerMethod handlerMethod = findHandlerMethodForRequest(request);

        if (handlerMethod != null) {
            // 找到了对应的处理器方法,使用它来处理 HEAD 请求
            if ("HEAD".equals(request.getMethod())) {
                return new HandlerMethodReturnValueHandlerComposite().handleReturnValue(
                        new ResponseEntity<>(null, handlerMethod.getResponseHeaders(), HttpStatus.OK), 
                        handlerMethod,
                        new ServletWebRequest(request)
                );
            }
            // ...其他请求方法的处理逻辑
        }

        // 没有找到合适的处理器方法,返回 404 Not Found 响应
        return new ResponseEntity(HttpStatus.NOT_FOUND);
    }

    // ...其他辅助方法
}

在上述代码中,当 DispatcherServlet 收到一个 HEAD 请求时,会调用 RequestMappingHandlerMapping 的getHandler() 方法进行处理。

如果存在对应的 GET 请求方法,RequestMappingHandlerMapping 就会将其包装成一个空的 ResponseBody 对象,并设置响应头信息,最终返回这个对象作为响应结果。如果没有找到对应的 GET 方法,则会直接返回一个 404 Not Found 响应。

image-20230413105030946

Gin

与 Spring Boot 不同,Gin 框架不会自动为 GET 请求生成 HEAD 请求的处理方法。因此,如果要支持 HEAD 请求,你需要为对应的请求路径显式地编写一个处理 HEAD 请求的函数。

举个例子,假设你要对某个请求路径"/example"实现 HEAD 请求方法,可以在路由中增加如下代码:

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()

    r.GET("/example", func(c *gin.Context) {
        // 这里是 GET 请求的业务逻辑
        c.String(http.StatusOK, "Hello, GET request!")
    })

    r.HEAD("/example", func(c *gin.Context) {
        // 这里是 HEAD 请求的业务逻辑
        c.Status(http.StatusOK)
    })

    r.Run(":8080")
}

如何使用curl发送HEAD请求

curl是一个非常强大的命令行工具,可以用来向服务器发送HTTP请求。要发送HEAD请求,只需使用"-I"选项即可。以下是一个使用curl发送HEAD请求的示例:

curl -I https://www.devzhi.com

上述命令将向http://www.devzhi.com发送一个HEAD请求,如果该资源存在,则服务器将返回响应头部信息。

$ curl -I https://www.devzhi.com
HTTP/1.1 404 Not Found
Server: nginx/1.18.0 (Ubuntu)
Date: Thu, 13 Apr 2023 02:52:52 GMT
Content-Type: text/plain
Content-Length: 18
Connection: keep-alive

注意到了吗,返回了404,因为目前我的博客是使用gin来实现的,而我没有去实现HEAD接口,这也是对前文关于Gin框架介绍部分的一个实际演示,什么?为什么文章一开头返回的是200 OK ?,那当然是为了把文章写下去啊。

HEAD请求的应用场景

  1. 检查资源是否存在:当你需要检查一个资源是否存在时,使用HEAD请求可以返回该资源的状态码和相关头信息,而无需下载整个资源。
  2. 获取资源的元数据:有时候,我们只需要获取资源的元数据,比如最后修改时间、大小等信息。HEAD请求可以帮助我们在不下载整个资源的情况下获取这些信息。
  3. 验证资源是否被修改:在某些情况下,我们需要在资源被修改后更新缓存。使用HEAD请求可以让我们检查资源的最后修改时间,从而判断是否需要更新缓存。
  4. 确认链接是否有效:当您需要在网站中创建链接时,使用HEAD请求可以验证链接是否指向有效的资源,本站对友情链接的检测便是采用的HEAD请求。