0. 背景
最近接到一个对接变更支持需求。业务场景是:我们去获取对方系统的附件。现有的交互逻辑是:
- 访问对方的接口,返回一个url链接,这个链接包含鉴权和附件信息,鉴权信息包含有效期
- 访问步骤1返回的url,返回IO流,从而获得附件
对接系统即将发生一个变更,即变更url链接,变更后的url也是返回IO流。于是笔者想,那这不关我们事啊,你保证url可以正常返回IO流就可以,毕竟步骤1的接口又没有变。
于是对方在测试环境开发后开始同我们联调,不出意外的话就是出意外了。。
死活就是获取不到附件,获取到的附件无法正常打开,使用VS Code打开后里面是错误的提示信息,提示鉴权不通过。
而现有的获取IO流的方式是使用RestTemplate,然后对接的同事让我用URL试试,说他们自己也是这么用的,结果就可以了。
1. 模拟
这里就省去具体的业务场景,也省去期间的各种推测、验证了,直接模拟最终的结果。
1.1 请求的url
先说明下get请求的url:
http://localhost:8080/tool/attachment/getIO/JhKYanCAt1NPTzlbSHgO8civur5LBZqy?signature=QUJD+REVGRw==&content=attachment%3Bfilename%3D%E9%83%BD%E6%98%AFGet%E8%AF%B7%E6%B1%82.md
说明如下,为了方便演示,请求参数设置为两个,主要是附件的信息和权限校验:
内容 | 参数 | 说明 |
---|---|---|
API接口 | /tool/attachment/getIO | 获取附件IO流 |
路径参数 - fileId | JhKYanCAt1NPTzlbSHgO8civur5LBZqy | 附件id |
请求参数 - signature | QUJD+REVGRw== | 签名 |
请求参数 - content | attachment…(省略) | 内容,看到很多%就推测是做了URL编码 |
1.2 服务端
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* Copyright: Horizon
*
* @ClassName AttachmentController
* @Description 附件控制层
* @Author Nile (QQEmail:576109623)
* @Date 12:56 2023/5/13
* @Version 1.0.0
*/
@Slf4j
@RestController
@RequestMapping("/tool/attachment")
public class AttachmentController {
/**
* 获取附件IO流
* @author Nile (QQEmail:576109623)
* @date 12:56 2023/5/13
* @param fileId 附件id
* @param signature 签名
* @param content 内容
* @return void
*/
@GetMapping("/getIO/{fileId}")
public String getAttachmentIO(@PathVariable String fileId, @RequestParam String signature,
@RequestParam String content) {
log.info("fileId: {}", fileId);
log.info("signature: {}", signature);
log.info("content: {}", content);
// 返回IO流,简单返回个提示
return "SUC0000";
}
}
1.3 客户端
package com.project.tool.controller;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import static org.springframework.http.HttpHeaders.CONNECTION;
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
/**
* Copyright: Horizon
*
* @ClassName AttachmentControllerTest
* @Description 附件测试类
* @Author Nile (QQEmail:576109623)
* @Date 13:26 2023/5/13
* @Version 1.0.0
*/
@Slf4j
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@RunWith(SpringRunner.class)
public class AttachmentControllerTest {
private static final String REQUEST_URL = "http://localhost:8080/tool/attachment/getIO" +
"/JhKYanCAt1NPTzlbSHgO8civur5LBZqy?signature=QUJD+REVGRw==&content=attachment%3Bfilename%3D%E9%83%BD%E6" + "%98%AFGet%E8%AF%B7%E6%B1%82.md";
/**
* 使用RestTemplate请求
* @Author Nile (QQEmail:576109623)
* @Date 16:39 2023/5/13
*/
@Test
public void getAttachmentWithRestTemplate() throws IOException {
HttpHeaders headers = new HttpHeaders();
headers.set(CONNECTION, "Keep-Alive");
headers.set(CONTENT_TYPE, "charset=UTF-8");
HttpEntity<Void> httpEntity = new HttpEntity<>(headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<Resource> response = restTemplate.getForEntity(REQUEST_URL, Resource.class, httpEntity);
Resource resource = response.getBody();
InputStream inputStream = resource.getInputStream();
}
/**
* 使用URL请求
* @Author Nile (QQEmail:576109623)
* @Date 16:39 2023/5/13
*/
@Test
public void getAttachmentWithURL() throws IOException {
URL urlObject = new URL(REQUEST_URL);
URLConnection urlConnection = urlObject.openConnection();
urlConnection.connect();
InputStream inputStream = urlConnection.getInputStream();
}
}
注:这里使用了两种方式进行get请求,其实还有第三种方式,就是浏览器直接访问这个url链接,毕竟在很多的业务场景下,需要点击直接下载附件,使用的也是get请求。
2. 模拟结果
直接说模拟结果了,其实就是url编码的问题。
请求的参数:
参数 | 值 |
---|---|
fileId | JhKYanCAt1NPTzlbSHgO8civur5LBZqy |
signature | QUJD+REVGRw== |
content | attachment%3Bfilename%3D%E9%83%BD%E6%98%AFGet%E8%AF%B7%E6%B1%82.md |
服务端接收到的参数:
请求方式 | 路径参数 - fileId | 请求参数 - signature | 请求参数 - content |
---|---|---|---|
RestTemplate | JhKYanCAt1NPTzlbSHgO8civur5LBZqy | QUJD REVGRw== | attachment%3Bfilename%3D%E9%83%BD%E6%98%AFGet%E8%AF%B7%E6%B1%82.md |
URL | JhKYanCAt1NPTzlbSHgO8civur5LBZqy | QUJD REVGRw== | attachment;filename=都是Get请求.md |
Chrome | JhKYanCAt1NPTzlbSHgO8civur5LBZqy | QUJD REVGRw== | attachment;filename=都是Get请求.md |
3. 结论
虽然上面的路径参数是UUID,但因为好奇,笔者还是换成了URL编码后的参数和带加号的参数做了测试。
-
对于路径参数,服务端原样接收,不会对参数进行URL解码,+也不会变为空格
-
对于请求参数:
a. RestTemplate,不会对参数进行URL解码,会原样传到服务端;
b. URL和Chrome,会对参数进行URL解码,服务端接收到的是解码后的数据;
c. 不论那种方式,+都会变为空格。(笔者之前总结过一篇日志:浏览器对url字符的处理,请参考指正)
好吧,也终于知道为什么对接系统给的新链接的参数要进行URL编码,就是为了防止+等特殊字符的转变问题。而使用RestTemplate因为没有进行URL解码,导致服务端接收的参数不对(服务端不会对接收到的参数进行解码),这就是为什么错误信息是鉴权不通过。不得已只能变更请求方式,进行了一次上线。