http请求参数中巨坑的“+”被转为空格的问题

http请求参数中巨坑的“+”被转为空格的问题

源码:https://gitee.com/qplo/rest-template
​ ,我们在通过 SpringBoot 提供的(客户端请求) RestTemplate 方法去请求其他服务的接口时,所带参数携带 “+” 号被转换成空格的问题

1.前提

  • 前提我们得先知道HTTP请求中携带的参数中带 “+” 请求后端时,在经过 tomcat 时,会被替换成空格。

通过debug我们来到

  • public final class Parameters 类中的 processParameters 方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AOXocLnG-1640452473873)(C:\Users\deku\AppData\Roaming\Typora\typora-user-images\image-20211225232948655.png)]

经过层层套娃之后他就会进入下面的方法中

  • public final class UDecoder{} 类中的 convert 方法
    在这里插入图片描述

这里详细看https://www.cnblogs.com/thisiswhy/p/12119126.html

上面我们知道 “+” 在经过 tomcat 处理过后会变成空格

2.了解转码,解码

	String encode = URLEncoder.encode("come +here=", "UTF-8");
        System.out.println(encode);
        String decode = URLDecoder.decode(encode, "UTF-8");
        System.out.println(decode);
        String decodeDemo = URLDecoder.decode("come +here=/yuftujy", "UTF-8");
        System.out.println(decodeDemo);
/*
*	输出
*	come+%2Bhere%3D
*	come +here=
*	come  here=/yuftujy
*/
tomcat转码解码
“ ”“ ”“+”“ ”
“+”“ ”“%2B”“+”
“=”“=”“%3D”“=”

我们这就发现一个空格的问题,他在转码后没有变成我们所预期的 “%20”,而是变成了 ”+“,而 ”+” 号在经过 tomcat 时又会变成 空格?

这会导致什么问题,后端怎么去处理空格?

这就是一个巨大的坑!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

3.发现问题

现在我们结合工作中的问题,在客户端,用SpringBoot提供的 RestTemplate 请求其他服务参数带 ”+“ 的问题、

  • 得知 admId 从请求开始到结束一共经过了两次 tomcat

在调试中我发现三种情况

1、正常请求:http://localhost:8080/cdata?admId=S9Y+qEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w=

@GetMapping("/cdata")
public Object cdata(@RequestParam String admId) throws Exception{
    
    System.out.println("tomcat转:"+admId);//S9Y qEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w=

    String url="http://localhost:8080/hello";
    UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(url)
            .queryParam("admId", encode);
    String urls = uriComponentsBuilder.build().toUriString();

    HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(null, null);
    ResponseEntity<ResponsVO> response = restTemplate.exchange(urls, HttpMethod.GET, requestEntity,
            new ParameterizedTypeReference<ResponsVO>() {
            });

    return response.getBody().getData();
}

@GetMapping("hello")
public Object cdataDemo(String admId) throws Exception{
    
    System.out.println(admId);//S9Y qEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w=
    
    ResponsVO<Object> responsVO = new ResponsVO<>();
    responsVO.setCode(200);
    responsVO.setMsg("成功");
    User user = new User();
    user.setId(1);
    user.setName("");
    responsVO.setData(user);
    return responsVO;
}

//正常请求我们可以看到参数 admId 第一次进过tomcat的时候 “+” 就变成了空格,肯定是不对的

2.第二种:加上转码解码

 @GetMapping("/cdata")
    public Object cdata(@RequestParam String admId) throws Exception{
        
        System.out.println("转码前:"+admId);//转码前:S9Y qEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w=

        String encode = URLEncoder.encode(admId, "UTF-8");
        
        System.out.println("转码后:"+encode);//转码后:S9Y+qEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w%3D

        String url="http://localhost:8080/hello";
        UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(url)
                .queryParam("admId", encode);
        String urls = uriComponentsBuilder.build().toUriString();
        HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(null, null);
        ResponseEntity<ResponsVO> response = restTemplate.exchange(urls, HttpMethod.GET, requestEntity,
                new ParameterizedTypeReference<ResponsVO>() {
                });

        return response.getBody().getData();
    }

    @GetMapping("hello")
    public Object cdataDemo(String admId) throws Exception{
        
        System.out.println("解码前:"+admId);
        //解码前:S9Y qEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w%3D
        System.out.println("解码后:"+URLDecoder.decode(admId,"UTF-8"));
        //解码后:S9Y qEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w=
        
        ResponsVO<Object> responsVO = new ResponsVO<>();
        responsVO.setCode(200);
        responsVO.setMsg("成功");
        User user = new User();
        user.setId(1);
        user.setName("");
        responsVO.setData(user);
        return responsVO;
    }
} 
/**
 * 转码前:S9Y qEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w=
 * 转码后:S9Y+qEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w%3D
 * 解码前:S9Y qEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w%3D
 * 解码后:S9Y qEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w=
 */
  • 还是不行,因为 admId经过第一次 tomcat的时候就已经把“+”替换成了空格,转码后“=”被符合预期的转成了"%3D",但是 空格 被转码后又变成了 “+” ,不是我们所预期的“%20”,这导致在第二次请求接口的时候 “+” 又被替换成了空格。
  • 这么一看,那他不是无解了吗???
  • 还有一种不得已的办法,直接把空格替换成 “+”

或者

3.第三种:前端传参时进行转码-S9Y%2BqEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w%3D

@GetMapping("/cdata")
    public Object cdata(@RequestParam String admId) throws Exception{
//        admId="admId%2BS9Y%2BqEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w%3D";
        
        System.out.println("转码前:"+admId);//转码前:S9Y+qEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w=
        String encode = URLEncoder.encode(admId, "UTF-8");
        System.out.println("转码后:"+encode);//转码后:S9Y%2BqEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w%3D

        String url="http://localhost:8080/hello";
        UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(url)
                .queryParam("admId", encode);
        String urls = uriComponentsBuilder.build().toUriString();
        HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(null, null);
        ResponseEntity<ResponsVO> response = restTemplate.exchange(urls, HttpMethod.GET, requestEntity,
                new ParameterizedTypeReference<ResponsVO>() {
                });

        return response.getBody().getData();
    }

    @GetMapping("hello")
    public Object cdataDemo(String admId) throws Exception{
        System.out.println("解码前:"+admId);
        //解码前:S9Y%2BqEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w%3D
        System.out.println("解码后:"+URLDecoder.decode(admId,"UTF-8"));
        //解码后:S9Y+qEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w=
        ResponsVO<Object> responsVO = new ResponsVO<>();
        responsVO.setCode(200);
        responsVO.setMsg("成功");
        User user = new User();
        user.setId(1);
        user.setName("");
        responsVO.setData(user);
        return responsVO;
    }
/**
 * 转码前:S9Y+qEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w=
 * 转码后:S9Y%2BqEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w%3D
 * 解码前:S9Y%2BqEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w%3D
 * 解码后:S9Y+qEERxxGVFo0DE8mruGVGmVYNJraVwZyijimEv6w=
 */
  • 从输出结果中我们可以看出,前端对参数进行转码后,“+” 被符合预期的转成了 “%2B”,进过tomcat的解码之后,再对其进行转码,在其他服务接口对其进行解码之后就能返回正常数据

  • 这里又发现一个问题,这次我们可以看到,第一次请求时,前端转码后的参数传到后端时被自动解码了,但是在第二次发起请求时,参数没有被自动解码,需要手动解码???

  • 原因:是因为 restTemplate 的问题,因为 restTemplate 发起get请求时,会对参数进行一次编码,tomcat又对其进行解码。但是这时候就会发现既然 restTemplate 会对其进行转码,那为什么还要用 encode 再对其进行一次转码? 因为restTemplate 的编码和 encode有所不同,restTemplate 不会对某些字符进行编码,例如 “+” 等,导致“+”在经过 tomcat 时被转成空格!!!
    https://www.jianshu.com/p/0bdcc6836eb3
    在这里插入图片描述
    总结:

  • 前端传参到后端,参数首先会经过 tomcat 的检验,tomcat 的 convert 的方法会对参数进行解码,并把“+”提换成空格

  • 如果用 restTemplate 去请求其他服务时,参数又会经过 restTemplate 的检验,restTemplate 会对其进行编码,但是有一点不同的是,不会对某些特殊字符进行编码。例如 “+” 等。

  • 请求到其他服务时参数又来到了 tomcat 这里经受检验,然后来到 controller 层

用 restTemplate 请求其他服务 的参数带有特殊字符时

解决办法:
1.

  • 前端对参数进行转码(把“+”转化为“%2B”)
  • 后端对参数进行一次转码,
  • 其他服务进行解码

前端转两次码
其他服务进行解码

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
TDengine是一种高性能、高可靠的时序数据库,由国企业开发而成。然而,有些用户认为TDengine存在许多问题,因此将其称为"巨坑"。以下是一些可能导致用户这样形容TDengine的问题: 首先,TDengine在与其他数据库集成时可能存在兼容性问题。由于其独特的架构和设计理念,一些已有的应用程序或工具可能无法直接适配TDengine。这可能导致用户需要进行大量的修改或重写现有代码,对于一些复杂的应用场景来说,这可能是一项耗时且繁琐的工作。 其次,TDengine的文档和教程相对较少。对于新用户来说,他们可能很难找到足够的资源来学习和理解TDengine的使用方法和最佳实践。这可能给用户带来一些困扰,特别是在遇到问题时很难找到解决办法。 此外,TDengine在某些方面的性能可能不如用户期望。虽然它被称为高性能数据库,但是与其他同类产品相比,TDengine可能在某些场景下的性能表现不如人意。这可能导致一些用户对TDengine的性能感到失望,并在使用过程遇到一些瓶颈。 最后,TDengine可能也存在一些稳定性问题。尽管它被标榜为高可靠性数据库,但在实际使用,一些用户可能遇到了一些无法解决的故障或崩溃问题。这可能对用户的业务和数据产生一定的影响,并损害用户对TDengine的信任度。 总之,尽管TDengine在性能和可靠性方面具备一定的优势,但也不能否认它目前还存在一些问题。用户在选择使用TDengine时需要充分了解其特点和局限性,并根据自身业务需求评估是否适合使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值