运维.售后
论坛
潜水/灌水快乐,沉淀知识,认识更多同行。
ToB圈子
加入IT圈,遇到更多同好之人。
朋友圈
看朋友圈动态,了解ToB世界。
博客
Blog
ToB门户
了解全球最新的ToB事件
排行榜
Ranklist
文库
业界最专业的IT文库,上传资料也可以赚钱
下载
分享
Share
导读
Guide
相册
Album
记录
Doing
搜索
本版
文章
帖子
ToB圈子
用户
免费入驻
产品入驻
解决方案入驻
公司入驻
案例入驻
登录
·
注册
只需一步,快速开始
账号登录
立即注册
找回密码
用户名
Email
自动登录
找回密码
密码
登录
立即注册
首页
找靠谱产品
找解决方案
找靠谱公司
找案例
找对的人
专家智库
悬赏任务
圈子
SAAS
ToB企服应用市场:ToB评测及商务社交产业平台
»
论坛
›
软件与程序人生
›
后端开发
›
Java
›
记一次 RestTemplate 请求失败问题的排查 → RestTempla ...
记一次 RestTemplate 请求失败问题的排查 → RestTemplate 默认会对特殊字 ...
宁睿
金牌会员
|
2024-1-13 05:38:54
|
显示全部楼层
|
阅读模式
楼主
主题
957
|
帖子
957
|
积分
2881
开心一刻
今天中午,侄子在沙发上玩手机,他妹妹屁颠屁颠的跑到他面前
小侄女:哥哥,给我一块钱
侄子:叫妈给你
小侄女朝着侄子,毫不犹豫的叫到:妈!
侄子:不是,叫妈妈给你
小侄女继续朝他叫到:妈妈
侄子受不了,从兜里掏出一块钱说道:我就只有这一块钱了,拿去拿去
小侄女最后还不忘感谢到:谢谢妈妈!
侄子彻底奔溃了,我在一旁笑出了鹅叫声
需求背景
需求很简单,就是以 HTTP 的方式下载 OSS 上的文件,类似如下
分两步
1、获取文件的下载地址( HTTP 地址 )
2、根据下载地址下载文件
第 1 步不是本文的重点,略过,我们只需要实现第 2 步,是不是很简单?
问题复现
目前,系统跟其他系统的 HTTP 对接都是用的 RestTemplate
那毫无疑问,也用 RestTemplate 来下载 OSS 文件
测试代码非常简单,如下
package com.qsl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
/**
* @description: RestTemplate 测试
* @author: 博客园@青石路
* @date: 2023/11/26 15:31
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class RestTemplateTest {
@Resource
private RestTemplate restTemplate;
@Test
public void testOss() {
String ossUrl = "https://qsl-yzb-test.oss-cn-wuhan-lr.aliyuncs.com/company_compare_t.sql?Expires=1700987277&OSSAccessKeyId=TMP.3Kf7vKYWL9RHkroENy7hUyrqAhHBC8YpBCnqXAstCyH3K1j6fkZujtL47V1mFkG5e5hmnLD2dVn4ZJGeD2yDh3GAAQc1k8&Signature=O2qiPYvfZyPmeouwzkXcNqC4Oy0%3D";
ResponseEntity<byte[]> responseEntity = restTemplate.getForEntity(ossUrl, byte[].class);
System.out.println(responseEntity.getStatusCode());
}
}
复制代码
View Code
我们看下执行结果,发现报异常了
org.springframework.web.client.HttpClientErrorException$Forbidden: 403 Forbidden: [<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>AccessDenied</Code>
<Message>Request has expired.</Message>
<RequestId>65630E3B05EC713334EDD93D</RequestId>
<HostId>qsl-yzb-test.oss-cn-wuh... (443 bytes)]
at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:109)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:170)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:112)
at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:785)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:743)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:677)
at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:345)
at com.qsl.RestTemplateTest.testOss(RestTemplateTest.java:27)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
复制代码
View Code
直接从浏览器下载是正常的,用代码走 RestTemplate 方式下载则失败,提示 403 Forbidden
是不是有点懵?
问题排查
系统中已经用 RestTemplate 对接了很多 HTTP 接口,全部都没问题
这不就是一个很简单的 HTTP 请求吗,简单的不能再简单了,怎么会失败了?
直接把我整不会了,不知道从何下手去排查了
第一时间想到了阿里云 OSS 售后,联系到人工客服,反馈了问题
客服响应倒是很及时,但却迟迟没有找到问题原因
然后我又将求助目光转向了部门内同事
有个同事提到:你开启 debug 日志,看看 RestTemplate 请求地址或参数是不是有什么问题
我内心其实是拒绝的, HTTP 地址都是现成的,都不用拼接, GET 方式的参数也是直接在 URL 中,能有什么问题?
但我的手却很诚实,默默的开启了 debug 日志(在配置文件中加上: debug: true )
执行结果依旧失败,但是多了三行 debug 日志
RestTemplate 的请求 URL 已经打印出来了,我们来和原始的 URL 对比一下,看看是不是有区别
不比不知道,一比吓一跳,这特喵的 RestTemplate 是做了手脚呀!对
%
进行了转义处理,处理成 %25 了
至于为什么需要对 GET 方式的 URL 的特殊字符进行转义,我就不做过多解释了(网上资料很多!),举个例子你们就明白了
http://localhost:8080/hello?name=青石路 的参数 name 的值是 青石路 ,这个大家都认可吧?
如果 name 的值是 青石路&路石青 ,这个 URL 应该是怎样的?
有人可能会有疑问了:你这说的是
&
,跟
%
有什么关系?
你是黑子,来搞我的吧?
求求你别搞我,我很菜的!
RFC 3986编码规范 指明了:百分号本身用作对不安全字符进行编码时使用的特殊字符,因此本身需要编码
例如: %20 表示空格, %2B 表示 +,等等
问题处理
问题已经找到了,那么该如何处理了?
抛开上面的问题,处理这种 URL 转义的问题,方式有很多
1、改成 POST 请求方式
比较推荐这种方式,奈何这种方式不适用本案例
2、使用 HttpClient jar
因为同事用的这种方式实现与本案例一样的下载,没有转义问题
但为了统一,仍想保留统一的 RestTemplate 方式,即没有采用这种方式
3、 RestTemplate 的 URI 方式
本案例最终采用这种的方式
通过 debug 日志是能够看到, RestTemplate 请求的地址是没有进行转义的(这里不展示了,大家自行去测试!)
至于 String 和 URI 的差别,大家去 debug 跟下源码就清楚了,底层的实现差别还是很大的哦
当然还有其他的方式,但是需要结合系统当前的情况,找出最合适的那种方式
总结
1、别自以为是,该试还得试
2、 debug 日志是调试的好东西,记得用、用、用!
3、多学多总结,多和同事分享沟通,有问题了才好请教他们
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
本帖子中包含更多资源
您需要
登录
才可以下载或查看,没有账号?
立即注册
x
回复
使用道具
举报
0 个回复
倒序浏览
返回列表
快速回复
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
or
立即注册
本版积分规则
发表回复
回帖并转播
回帖后跳转到最后一页
发新帖
回复
宁睿
金牌会员
这个人很懒什么都没写!
楼主热帖
java前置学习
【RocketMQ】消息的存储
简单的用Python对手机号进行加密 ...
k8s v-1.20版本部署详细过程[实测可用 ...
【PostgreSQL】PostgreSQL重建与主库不 ...
iOS Widget
基于单片机的压力测控仿真设计(#0024) ...
❤️肝下25万字的《决战Linux到精通》 ...
Unity 将是驱动 C# 增长的引擎吗 ? ...
net core 3.1使用identityServer登录时 ...
标签云
存储
服务器
快速回复
返回顶部
返回列表