当前位置: 首页 > article >正文

OpenFeign不支持{}特殊字符的header解决

一、环境

    <properties><spring.version>5.3.22</spring.version><spring-boot.version>2.7.3</spring-boot.version><spring-cloud.version>3.1.3</spring-cloud.version><spring-cloud-dependencies.version>2021.0.3</spring-cloud-dependencies.version><spring-cloud-starter-alibaba.version>2021.0.1.0</spring-cloud-starter-alibaba.version></properties>

其中feign包的版本号

      <dependency><groupId>io.github.openfeign</groupId><artifactId>feign-bom</artifactId><version>11.8</version><type>pom</type><scope>import</scope></dependency>

二、场景描述

feign需要传递一些json格式的数据,代码如下

@Slf4j
public class AuthFeignInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate requestTemplate) {UserDetail userDetail = UserContext.getUserDetail();if (Objects.nonNull(appDetail)) {String userJson = JsonUtils.toJsonString(userDetail);requestTemplate.header(Constant.User.HEADER_NAME, userJson);}}
}

三、 问题定位

userJson携带了特殊符号$:,断点调试的时候参数都是正常设置,尝试定位是生产者还是消费者的问题,使用postman模拟消费者调用,生产者可以正常收到信息并解析成功,那么问题就在消费者

尝试源码断点


public final class RequestTemplate implements Serializable {public RequestTemplate header(String name, String... values) {return header(name, Arrays.asList(values));}public RequestTemplate header(String name, Iterable<String> values) {if (name == null || name.isEmpty()) {throw new IllegalArgumentException("name is required.");}if (values == null) {values = Collections.emptyList();}return appendHeader(name, values);}private RequestTemplate appendHeader(String name, Iterable<String> values) {if (!values.iterator().hasNext()) {/* empty value, clear the existing values */this.headers.remove(name);return this;}if (name.equals("Content-Type")) {// a client can only produce content of one single type, so always override Content-Type and// only add a single typethis.headers.remove(name);this.headers.put(name,HeaderTemplate.create(name, Collections.singletonList(values.iterator().next())));return this;}this.headers.compute(name, (headerName, headerTemplate) -> {if (headerTemplate == null) {return HeaderTemplate.create(headerName, values);} else {return HeaderTemplate.append(headerTemplate, values);}});return this;}
}

可以看到最终是调用HeaderTemplate来实现header设置,继续查看HeaderTemplate源码

public final class HeaderTemplate {public static HeaderTemplate create(String name, Iterable<String> values) {if (name == null || name.isEmpty()) {throw new IllegalArgumentException("name is required.");}if (values == null) {throw new IllegalArgumentException("values are required");}return new HeaderTemplate(name, values, Util.UTF_8);}private HeaderTemplate(String name, Iterable<String> values, Charset charset) {this.name = name;for (String value : values) {if (value == null || value.isEmpty()) {/* skip */continue;}this.values.add(new Template(value,ExpansionOptions.REQUIRED,EncodingOptions.NOT_REQUIRED,false,charset));}}
}

可以看到HeaderTemplate只是对Template进行封装


public class Template {Template(String value, ExpansionOptions allowUnresolved, EncodingOptions encode, boolean encodeSlash,Charset charset) {if (value == null) {throw new IllegalArgumentException("template is required.");}this.template = value;this.allowUnresolved = ExpansionOptions.ALLOW_UNRESOLVED == allowUnresolved;this.encode = encode;this.encodeSlash = encodeSlash;this.charset = charset;// 解析${}占位符this.parseTemplate();}private void parseTemplate() {// 解析{}占位符this.parseFragment(this.template);}private void parseFragment(String fragment) {// 解析每个{}占位符ChunkTokenizer tokenizer = new ChunkTokenizer(fragment);while (tokenizer.hasNext()) {/* check to see if we have an expression or a literal */String chunk = tokenizer.next();// 如果占位符以{起始,则默认使用模板解析if (chunk.startsWith("{")) {Expression expression = Expressions.create(chunk);if (expression == null) {this.templateChunks.add(Literal.create(this.encodeLiteral(chunk)));} else {this.templateChunks.add(expression);}} else {this.templateChunks.add(Literal.create(this.encodeLiteral(chunk)));}}}
}

到这里基本已经定位到问题是feign实现了高级特性,占位符和模板解析造成的问题,具体还是:的特殊处理,暂时不展开

四、解决

定位到了问题,尝试找解决方案

4.1 占位符设置参数进行替换

查看RestTemplate源码发现有一个resolve方法可以对uriTemplatequeriesheaders进行参数替换

public RequestTemplate resolve(Map<String, ?> variables) {StringBuilder uri = new StringBuilder();/* create a new template form this one, but explicitly */RequestTemplate resolved = RequestTemplate.from(this);if (this.uriTemplate == null) {/* create a new uri template using the default root */this.uriTemplate = UriTemplate.create("", !this.decodeSlash, this.charset);}String expanded = this.uriTemplate.expand(variables);if (expanded != null) {uri.append(expanded);}/** for simplicity, combine the queries into the uri and use the resulting uri to seed the* resolved template.*/if (!this.queries.isEmpty()) {/** since we only want to keep resolved query values, reset any queries on the resolved copy*/resolved.queries(Collections.emptyMap());StringBuilder query = new StringBuilder();Iterator<QueryTemplate> queryTemplates = this.queries.values().iterator();while (queryTemplates.hasNext()) {QueryTemplate queryTemplate = queryTemplates.next();String queryExpanded = queryTemplate.expand(variables);if (Util.isNotBlank(queryExpanded)) {query.append(queryExpanded);if (queryTemplates.hasNext()) {query.append("&");}}}String queryString = query.toString();if (!queryString.isEmpty()) {Matcher queryMatcher = QUERY_STRING_PATTERN.matcher(uri);if (queryMatcher.find()) {/* the uri already has a query, so any additional queries should be appended */uri.append("&");} else {uri.append("?");}uri.append(queryString);}}/* add the uri to result */resolved.uri(uri.toString());/* headers */if (!this.headers.isEmpty()) {/** same as the query string, we only want to keep resolved values, so clear the header map on* the resolved instance*/resolved.headers(Collections.emptyMap());for (HeaderTemplate headerTemplate : this.headers.values()) {/* resolve the header */String header = headerTemplate.expand(variables);if (!header.isEmpty()) {/* append the header as a new literal as the value has already been expanded. */resolved.header(headerTemplate.getName(), header);}}}if (this.bodyTemplate != null) {resolved.body(this.bodyTemplate.expand(variables));}/* mark the new template resolved */resolved.resolved = true;return resolved;}

但是RequestTemplate对象是新生成的,无法进行传递

4.2 替换RequestTemplate的HeaderTemplate

查看源码发现RequestTemplate并没有开发对HeaderTemplate直接注入方法,所有header都是使用header()方法进行处理,而对RequestTemplate的修改都是新生成一个RequestTemplate,放弃这个方案

4.3 参数编码

所有签名都是基于Base64进行字节数组编码,那么该方案的适应性是最好的,对于http协议支持最好,修改源码

@Slf4j
public class AuthFeignInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate requestTemplate) {UserDetail userDetail = UserContext.getUserDetail();if (Objects.nonNull(appDetail)) {String userJson = JsonUtils.toJsonString(userDetail);String encodeAppJson = Base64.encode(appJson);requestTemplate.header(Constant.User.HEADER_NAME, encodeAppJson);}}
}

参数解析可以Base64.decode完成解析,还可以兼容中文,不要再进行UTF-8编码,如果考虑历史兼容性,可以先判断header是否以{起始,如果不是则使用Base64解析,如果考虑协议层的可变更性,可以在header中接入类似content-type的编码类型,来实现对变化的支持

五、参考

issue

http://www.lryc.cn/news/2419924.html

相关文章:

  • c语言中pause的作用,c++中的system(pause)的作用和含义解析
  • 微信小程序_介绍
  • 非诚勿扰又来一男程序员
  • 深度全方位盘点你眼中的IT行业现状与未来趋势
  • BZOJ 2462 BeiJing 2011 矩阵模板 二维hash
  • 2023计算机毕业设计SSM最新选题之java体育运动兴趣社区系统8bisy
  • CSS3:3D移动translate3d及3D转换透视效果perspective
  • 分布式系统架构网络之IDC机房
  • 靶向代谢组
  • 【UWB 定位】高精度定位
  • js获取数组长度-length属性的介绍
  • 专访 SphereEx 创始团队:获数百万美金投资,接棒 ShardingSphere 打造全新分布式生态
  • SpringBoot+Flowable 完美结合,优雅实现工作流!
  • EWSA破解WPA无线密码具体图文教程
  • 抓取静态网页数据
  • Hyperledger Fabric2.3 环境搭建及Fabric 测试网络使用
  • 初步了解SequoiaDB数据库
  • CSS3动画—— transition
  • ext-gwt与gwt-ext的区别
  • 7.1创新Audigy2Z S 7.1声卡的设置方法
  • 两个线程实现AABBCCDD
  • app测试系列-超详细的app测试攻略,一文带你学会移动端测试
  • 软件应用技巧二十二则
  • 好玩的100个网站收藏
  • 【腾讯云云上实验室】——向量数据库——Web端操作
  • 决策支持系统(DSS)介绍
  • 【C语言】静态函数(static)
  • JDBC 之ResultSetMetaData获取列名字
  • sockaddr和sockaddr_in的说明以及inet_pton和inet_ntop
  • Jsp(主要内容)