接受Header使用错Map类型,导致获取到的Header值不全

news2025/2/25 3:04:36

问题复现

  • 在 Spring 中解析 Header 时,我们在多数场合中是直接按需解析的。例如,我们想使用一个名为 myHeaderName 的 Header,我们会书写代码如下:
    @RequestMapping(path = "/hi", method = RequestMethod.GET)
    public String hi(@RequestHeader("myHeaderName") String name){
       //省略 body 处理
    };
    
  • 定义一个参数,标记上 @RequestHeader,指定要解析的 Header 名即可。但是假设我们需要解析的 Header 很多时,按照上面的方式很明显会使得参数越来越多。在这种情况下,我们一般都会使用 Map 去把所有的 Header 都接收到,然后直接对 Map 进行处理。于是我们可能会写出下面的代码:
    @RequestMapping(path = "/hi1", method = RequestMethod.GET)
    public String hi1(@RequestHeader() Map map){
        return map.toString();
    };
    
  • 粗略测试程序,你会发现一切都很好。而且上面的代码也符合针对接口编程的范式,即使用了 Map 这个接口类型。但是上面的接口定义在遇到下面的请求时,就会超出预期。请求如下:

    GET http://localhost:8080/hi1
    myheader: h1
    myheader: h2

  • 这里存在一个 Header 名为 myHeader,不过这个 Header 有两个值。此时我们执行请求,会发现返回的结果并不能将这两个值如数返回。结果示例如下:

    {myheader=h1, host=localhost:8080, connection=Keep-Alive, user-agent=Apache-HttpClient/4.5.12 (Java/11.0.6), accept-encoding=gzip,deflate}

案例解析

  • 实际上,当我们看到这个测试结果,大多数同学已经能反应过来了。对于一个多值的 Header,在实践中,通常有两种方式来实现,一种是采用下面的方式:

    Key: value1,value2,

  • 而另外一种方式就是我们测试请求中的格式:

    Key:value1
    Key:value2

  • 对于方式 1,我们使用 Map 接口自然不成问题。但是如果使用的是方式 2,我们就不能拿到所有的值。这里我们可以翻阅代码查下 Map 是如何接收到所有请求的。

  • 对于一个 Header 的解析,主要有两种方式,分别实现在 RequestHeaderMethodArgumentResolver 和 RequestHeaderMapMethodArgumentResolver 中,它们都继承于 AbstractNamedValueMethodArgumentResolver,但是应用的场景不同,我们可以对比下它们的 supportsParameter(),来对比它们适合的场景:
    在这里插入图片描述

  • 在上图中,左边是 RequestHeaderMapMethodArgumentResolver 的方法。通过比较可以发现,对于一个标记了 @RequestHeader 的参数,如果它的类型是 Map,则使用 RequestHeaderMapMethodArgumentResolver,否则一般使用的是 RequestHeaderMethodArgumentResolver。

  • 在我们的案例中,很明显,参数类型定义为 Map,所以使用的自然是 RequestHeaderMapMethodArgumentResolver。接下来,我们继续查看它是如何解析 Header 的,关键代码参考 resolveArgument():

    @Override
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
          NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
       Class<?> paramType = parameter.getParameterType();
       if (MultiValueMap.class.isAssignableFrom(paramType)) {
          MultiValueMap<String, String> result;
          if (HttpHeaders.class.isAssignableFrom(paramType)) {
             result = new HttpHeaders();
          }
          else {
             result = new LinkedMultiValueMap<>();
          }
          for (Iterator<String> iterator = webRequest.getHeaderNames(); iterator.hasNext();) {
             String headerName = iterator.next();
             String[] headerValues = webRequest.getHeaderValues(headerName);
             if (headerValues != null) {
                for (String headerValue : headerValues) {
                   result.add(headerName, headerValue);
                }
             }
          }
          return result;
       }
       else {
          Map<String, String> result = new LinkedHashMap<>();
          for (Iterator<String> iterator = webRequest.getHeaderNames(); iterator.hasNext();) {
             String headerName = iterator.next();
             //只取了一个“值”
             String headerValue = webRequest.getHeader(headerName);
             if (headerValue != null) {
                result.put(headerName, headerValue);
             }
          }
          return result;
       }
    }
    
  • 针对我们的案例,这里并不是 MultiValueMap,所以我们会走入 else 分支。这个分支首先会定义一个 LinkedHashMap,然后将请求一一放置进去,并返回。其中第 29 行是去解析获取 Header 值的实际调用,在不同的容器下实现不同。例如在 Tomcat 容器下,它的执行方法参考 MimeHeaders#getValue:

    public MessageBytes getValue(String name) {
        for (int i = 0; i < count; i++) {
            if (headers[i].getName().equalsIgnoreCase(name)) {
                return headers[i].getValue();
            }
        }
        return null;
    }
    
  • 当一个请求出现多个同名 Header 时,我们只要匹配上任何一个即立马返回。所以在本案例中,只返回了一个 Header 的值。

  • 其实换一个角度思考这个问题,毕竟前面已经定义的接收类型是 LinkedHashMap,它的 Value 的泛型类型是 String,也不适合去组织多个值的情况。综上,不管是结合代码还是常识,本案例的代码都不能获取到 myHeader 的所有值。

问题修正

  • 现在我们要修正这个问题。在案例解析部分,其实已经给出了答案。

  • 在 RequestHeaderMapMethodArgumentResolver 的 resolveArgument() 中,假设我们的参数类型是 MultiValueMap,我们一般会创建一个 LinkedMultiValueMap,然后使用下面的语句来获取 Header 的值并添加到 Map 中去:String[] headerValues = webRequest.getHeaderValues(headerName)

  • 参考上面的语句,不用细究,我们也能看出,我们是可以获取多个 Header 值的。另外假设我们定义的是 HttpHeaders(也是一种 MultiValueMap),我们会直接创建一个 HttpHeaders 来存储所有的 Header。

  • HttpHeaders 来存储所有的 Header。有了上面的解析,我们可以得出这样一个结论:要完整接收到所有的 Header,不能直接使用 Map 而应该使用 MultiValueMap。我们可以采用以下两种方式来修正这个问题:

    //方式 1
    @RequestHeader() MultiValueMap map
    //方式 2
    @RequestHeader() HttpHeaders map
    
  • 重新运行测试,你会发现结果符合预期:

    [myheader:“h1”, “h2”, host:“localhost:8080”, connection:“Keep-Alive”, user-agent:“Apache-HttpClient/4.5.12 (Java/11.0.6)”, accept-encoding:“gzip,deflate”]

  • 对比来说,方式 2 更值得推荐,因为它使用了大多数人常用的 Header 获取方法,例如获取 Content-Type 直接调用它的 getContentType() 即可,诸如此类,非常好用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2270697.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GitHub的简单操作

引言 今天开始就要开始做项目了&#xff0c;上午是要把git搭好。搭的过程中遇到好多好多的问题。下面就说一下git的简单操作流程。我们是使用的GitHub,下面也就以这个为例了 一、GitHub账号的登录注册 https://github.com/ 通过这个网址可以来到GitHub首页 点击中间绿色的S…

【时时三省】(C语言基础)常见的动态内存错误

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 对NULL指针的解引用操作 示例&#xff1a; malloc申请空间的时候它可能会失败 比如我申请一块非常大的空间 那么空间可能就会开辟失败 正常的话要写一个if&#xff08;p&#xff1d;&#x…

【51项目】51单片机自制小霸王游戏机

视频演示效果&#xff1a; 纳新作品——小霸王游戏机 目录&#xff1a; 目录 视频演示效果&#xff1a; 目录&#xff1a; 前言&#xff1a; 一、连接方式&#xff1a; 1.1 控制引脚 1.2. 显示模块 1.3. 定时器 1.4. 游戏逻辑与硬件结合 1.5. 中断处理 二、源码分析&#xff1a…

ESP32-S3遇见OpenAI:OpenAI官方发布ESP32嵌入式实时RTC SDK

目录 OpenAI RTC SDK简介应用场景详解智能家居控制系统个人健康助手教育玩具 技术亮点解析低功耗设计快速响应高精度RTC安全性保障开发者指南 最近&#xff0c;OpenAI官方发布了一款针对ESP32-S3的嵌入式实时RTC&#xff08;实时时钟&#xff09;SDK&#xff0c;这标志着ESP32-…

Elasticsearch:减少 Elastic 容器镜像中的 CVE(常见的漏洞和暴露)

作者&#xff1a;来自 Elastic Maxime Greau 在这篇博文中&#xff0c;我们将讨论如何通过在 Elastic 产品中切换到最小基础镜像并优化可扩展漏洞管理程序的工作流程来显著减少 Elastic 容器镜像中的常见漏洞和暴露 (Common Vulnerabilities and Exposures - CVEs)。 基于 Chai…

计算机网络 (21)网络层的几个重要概念

前言 计算机网络中的网络层是OSI&#xff08;开放系统互连&#xff09;模型中的第三层&#xff0c;也是TCP/IP模型中的第二层&#xff0c;它位于数据链路层和传输层之间&#xff0c;负责数据包从源主机到目的主机的路径选择和数据转发。 一、网络层的主要功能 路由选择&#xf…

LED背光驱动芯片RT9293应用电路

一&#xff09;简介&#xff1a; RT9293 是一款高频、异步的 Boost 升压型 LED 定电流驱动控制器&#xff0c;其工作原理如下&#xff1a; 1&#xff09;基本电路结构及原理 RT9293的主要功能为上图的Q1. Boost 电路核心原理&#xff1a;基于电感和电容的特性实现升压功能。当…

第四届计算机、人工智能与控制工程

第四届计算机、人工智能与控制工程 The 4th International Conference on Computer, Artificial Intelligence and Control Engineering 重要信息 大会官网&#xff1a;www.ic-caice.net 大会时间&#xff1a;2025年1月10-12日 大会地点&#xff1a;中国合肥 (安徽大学磬苑…

【Rust 学习笔记】Rust 基础数据类型介绍——指针、元组和布尔类型

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 博客内容主要围绕&#xff1a; 5G/6G协议讲解 高级C语言讲解 Rust语言讲解 文章目录 Rust 基础数据类型介绍——指针、元组和布尔类型一、元组类型…

YOLO系列的学习

YOLOV1全解 You Only Look Once&#xff0c;把检测问题转化成回归问题&#xff0c;一个CNN就搞定了&#xff01;&#xff01;&#xff01;效率高&#xff0c;可对视频进行实时检测&#xff0c;应用领域非常广&#xff0c;到V3的时被美国军方用于军事行动&#xff0c;作者出于某…

鸿蒙应用开发搬砖经验之—使用DevTools工具调试前端页面

环境说明&#xff1a; 系统环境&#xff1a;Mac mini M2 14.5 (23F79) 开发IDE&#xff1a;DevEco Studio 5.0.1 Release 配置步骤&#xff1a; 按着官方的指引来慢慢一步一步来&#xff0c;但前提是要配置好SDK的路径&#xff08;没有配置的话&#xff0c;可能先看下面的配…

计算机网络练习题

学习这么多啦&#xff0c;那就简单写几个选择题巩固一下吧&#xff01; 1. 在IPv4分组各字段中&#xff0c;以下最适合携带隐藏信息的是&#xff08;D&#xff09; A、源IP地址 B、版本 C、TTL D、标识 2. OSI 参考模型中&#xff0c;数据链路层的主要功能是&#xff08;…

Django REST framework 源码剖析-视图类详解(Views)

Django REST framework视图图解 视图类&#xff08;View&#xff09; ‌视图‌是DRF中处理用户请求的基本单元。它们可以是函数视图&#xff08;FBV&#xff09;或类视图&#xff08;CBV&#xff09;。函数视图使用函数来处理请求&#xff0c;而类视图则使用类来处理请求。类视…

spring中使用@Validated,什么是JSR 303数据校验,spring boot中怎么使用数据校验

文章目录 一、JSR 303后台数据校验1.1 什么是 JSR303&#xff1f;1.2 为什么使用 JSR 303&#xff1f; 二、Spring Boot 中使用数据校验2.1 基本注解校验2.1.1 使用步骤2.1.2 举例Valid注解全局统一异常处理 2.2 分组校验2.2.1 使用步骤2.2.2 举例Validated注解Validated和Vali…

网页单机版五子棋小游戏项目练习-初学前端可用于练习~

今天给大家分享一个 前端练习的项目&#xff0c;技术使用的是 html css 和javascrpit 。希望能对于 刚刚学习前端的小伙伴一些帮助。 先看一下 实现的效果图 1. HTML&#xff08;HyperText Markup Language&#xff09; HTML 是构建网页的基础语言&#xff0c;它的主要作用是定…

UE5材质节点Distance

Distance可以计算两个物体间的距离&#xff0c;可以用来做过渡效果 当相机和物体距离3000的时候&#xff0c;就会渐渐从蓝过渡到红色&#xff0c;除以500是为了平滑过渡

【AIGC-ChatGPT进阶提示词指令】AI美食助手的设计与实现:Lisp风格系统提示词分析

引言 在人工智能助手的应用领域中&#xff0c;美食烹饪是一个既专业又贴近生活的方向。本文将详细分析一个基于Lisp风格编写的美食助手系统提示词&#xff0c;探讨其结构设计、功能实现以及实际应用效果。 提出你的菜系&#xff0c;为你分析&#xff0c;并生成图片卡片 提示词…

基于开发/发布/缺陷分离模型的 Git 分支管理实践20250103

基于开发/发布/缺陷分离模型的 Git 分支管理实践 引言 在现代软件开发中&#xff0c;合理的分支管理策略是保证项目成功的关键因素之一。本文将详细介绍一种基于开发/发布/缺陷分离的 Git 分支管理模型&#xff0c;这种模型不仅能提升团队协作效率&#xff0c;还能确保代码质…

【Cocos TypeScript 零基础 3.1】

目录 场景跳转 场景跳转 把新建好的TS文件与场景绑定 选中 场景 或 camera 拖进右边的 属性检查器 双击T文件,进入编辑 至于用什么IDE看个位朋友高兴 我这里有 VScode ,先用这个,老师也没有推荐 (老师也用的是这个) VScode UI 也有中文包,请自行上网搜索 打开创建的TS文件后…

SAP SD学习笔记23 - 无偿出荷(免费交货)与继续无偿出荷(继续免费交货)

上一章讲了SAP中的一括请求处理。 SAP SD学习笔记22 - VF04&#xff0c;VF06&#xff0c;VF24 等一括请求处理-CSDN博客 本章继续讲SAP中的内容&#xff1a;无偿出荷 和 继续无偿出荷。 - 无偿出荷本身是挺常用的&#xff0c;常见的例子就是送给客户样品&#xff1b; - 继续…