监听 RabbitMQ 延时交换机的消息数、OpenFeign 路径参数传入斜杠无法正确转义

背景

【MQ】一套为海量消息和高并发热点消息,提供高可用精准延时服务的解决方案

我现在有一个需求,就是监听 RabbitMQ 一个延时交换机的消息数,而 RabbitTemplate 是不存在对应的方法来获取的。

而我们在 RabbitMQ 的控制台却可以发现延时交换机的消息数,所以其开放的 http-api 里存在我们需要的数据,通过抓包可得:

而我们查看这个包,构造请求(抓包+分析的技巧这里不做介绍)

当然你完全可以去看 RabbitMQ 的 http-api 开放文档,但是我觉得有点多,还不如直接抓包

URL:

  • http://rabbithost:15672/api/exchanges/{virtualHost}/{exchange}?msg_rates_age=60&msg_rates_incr=5

Method:

  • GET

Header:

  • Authorization: "Basic " + EncryptUtil.encodeBase64(String.format("%s:%s", rabbitMQConfig.getUsername(), rabbitMQConfig.getPassword()));

很快我们就能写一个 OpenFeign 客户端:

java 复制代码
@FeignClient(name = "rabbitmq-service", url = "${okr.mq.http-api}")
public interface RabbitMQHttpFeignClient {

    @GetMapping("/exchanges/{virtualHost}/{exchange}?msg_rates_age=60&msg_rates_incr=5")
    DelayExchangeVO getMessagesDelayed(@RequestHeader(HttpHeaders.AUTHORIZATION) String authorization,
                                       @PathVariable("virtualHost") String virtualHost,
                                       @PathVariable("exchange") String exchange);

}

但是你会发现,virtualHost 是带 / 的,但是最终的 url 并没有转义,导致路由出错报了 404

  • 400 是参数未通过验证、401 未通过身份认证、403 无权限

先说结论!!!

配置一个 Contract (协议,约定),并设置 decodeSlash 为 false !

java 复制代码
@Component
public class OpenFeignConfig {

    @Bean
    public Contract notdecodeSlashContract(){
        // 无自定义处理器、默认的 ConversionService、取消 %2F -> / 的解码
        return new SpringMvcContract(Collections.emptyList(), new DefaultConversionService(), Boolean.FALSE);
    }

}

decodeSlash,直译就是"斜杠解码"

encode: /%2F

decode: %2F/

而我们就是阻止 %2F/ ,那我们为什么要阻止呢?

问题分析

首先我们可能会想,它是如何转义的,是传入的时候转义,还是最终一起转义:

如果是最终一起转义,那 / 必然不能被转义,否则那些路由都会失效,所以如果是最终转义,无法满足我们的需求:

这里写了个简单的方法,方便理解

java 复制代码
public static <P> String buildUrl(String baseUrl, Map<String, List<String>> queryParams, Map<String, P> pathParams) {
    queryParams = Optional.ofNullable(queryParams).orElseGet(Map::of);
    pathParams = Optional.ofNullable(pathParams).orElseGet(Map::of);
    return UriComponentsBuilder
            .fromHttpUrl(baseUrl)
            .queryParams(new LinkedMultiValueMap<>(queryParams))
            .buildAndExpand(pathParams)
            .encode() // 开启译码模式
            .toUriString();
}

如果在传入的时候转义,才能实现我们的效果:

java 复制代码
public static <P> String buildUrl(String baseUrl, Map<String, List<String>> queryParams, Map<String, P> pathParams) {
    queryParams = Optional.ofNullable(queryParams).orElseGet(Map::of);
    pathParams = Optional.ofNullable(pathParams).orElseGet(Map::of);
    return UriComponentsBuilder
            .fromHttpUrl(baseUrl)
            .encode() // 开启译码模式,这里之后路径参数,/ 也会被转义为 %2F!
            .queryParams(new LinkedMultiValueMap<>(queryParams))
            .buildAndExpand(pathParams)
            .toUriString();
}

那 OpenFeign 是哪种呢?如果我们没看源码,我们可能没法判断,但我们可以知道,OpenFeign 在解析路径参数的时候,用的是 PathVariableParameterProcessor

参考文章:文章

通过自定义注解 + 自定义处理器的方式,处理请求,我们通过:

data.indexToExpander().put(context.getParameterIndex(), o -> URLEncoder.encode(String.valueOf(o), Charset.defaultCharset());

我们给 {name} 对应的 index 提供了一个解析器,但是貌似没啥用,如果进行双重编码,导致 % 也也被转义了,但如果只是一重编码,最终 / 还是以 / 的形式出现

这一度让我觉得是玄学!

但我对比了 PathVariableParameterProcessor 类的实现,发现其并没有专门对字符串进行编码,所以我猜测底层是定然编码了的,所以我进行了调试,一步步找到了关键代码:

你会发现,如果传入 / 会被转义成 %2F 也就是说,传入时确实已经编码了,你甚至可以实现传入 %2F 但并设置其已编码,所以不会再次编码,等等无论如何各种方式让字符串为 %2F

但是这里有一个属性 encodeSlash,如果为 false,则将最终结果的 %2F 给重新解码成 /

  • 说实话我完全不知道为啥要这样,太放剑了🤣
  • 如果是路径参数也是个 uri,也有这样的编程方式,但是我觉得很不规范

这也是我不熟悉 SpringMvcContract 导致的啦,不知道还有这么一个参数 decodeSlash

new SpringMvcContract(Collections.emptyList(), new DefaultConversionService(), false)

decodeSlash 设置为 false 后,encodeSlash 就为 true,%2F 就不会重新解码成 / 了,最终也就能达到我们的预期的效果了

相关推荐
记得开心一点嘛1 小时前
使用MinIO搭建自己的分布式文件存储
分布式·spring cloud·minio
纪元A梦3 小时前
分布式拜占庭容错算法——PBFT算法深度解析
java·分布式·算法
卿着飞翔3 小时前
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
java·rabbitmq·java-rabbitmq
计算机毕设定制辅导-无忧学长4 小时前
从 AMQP 到 RabbitMQ:核心组件设计与工作原理(二)
网络·rabbitmq·ruby
TCChzp8 小时前
Kafka入门-消费者
分布式·kafka
FakeOccupational11 小时前
【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 & 定向转发机制
笔记·分布式·p2p
·云扬·13 小时前
【PmHub面试篇】性能监控与分布式追踪利器Skywalking面试专题分析
分布式·面试·skywalking
后端码匠13 小时前
Spark 单机模式部署与启动
大数据·分布式·spark
Dnui_King14 小时前
Kafka 入门指南与一键部署
分布式·kafka