都是Get请求,RestTemplate和URL不一样?

0. 背景

最近接到一个对接变更支持需求。业务场景是:我们去获取对方系统的附件。现有的交互逻辑是:

  1. 访问对方的接口,返回一个url链接,这个链接包含鉴权和附件信息,鉴权信息包含有效期
  2. 访问步骤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流
路径参数 - fileIdJhKYanCAt1NPTzlbSHgO8civur5LBZqy附件id
请求参数 - signatureQUJD+REVGRw==签名
请求参数 - contentattachment…(省略)内容,看到很多%就推测是做了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编码的问题。

请求的参数:

参数
fileIdJhKYanCAt1NPTzlbSHgO8civur5LBZqy
signatureQUJD+REVGRw==
contentattachment%3Bfilename%3D%E9%83%BD%E6%98%AFGet%E8%AF%B7%E6%B1%82.md

服务端接收到的参数:

请求方式路径参数 - fileId请求参数 - signature请求参数 - content
RestTemplateJhKYanCAt1NPTzlbSHgO8civur5LBZqyQUJD REVGRw==attachment%3Bfilename%3D%E9%83%BD%E6%98%AFGet%E8%AF%B7%E6%B1%82.md
URLJhKYanCAt1NPTzlbSHgO8civur5LBZqyQUJD REVGRw==attachment;filename=都是Get请求.md
ChromeJhKYanCAt1NPTzlbSHgO8civur5LBZqyQUJD REVGRw==attachment;filename=都是Get请求.md

3. 结论

虽然上面的路径参数是UUID,但因为好奇,笔者还是换成了URL编码后的参数和带加号的参数做了测试。

  1. 对于路径参数,服务端原样接收,不会对参数进行URL解码,+也不会变为空格

  2. 对于请求参数:

    a. RestTemplate,不会对参数进行URL解码,会原样传到服务端;

    b. URL和Chrome,会对参数进行URL解码,服务端接收到的是解码后的数据;

    c. 不论那种方式,+都会变为空格。(笔者之前总结过一篇日志:浏览器对url字符的处理,请参考指正)

好吧,也终于知道为什么对接系统给的新链接的参数要进行URL编码,就是为了防止+等特殊字符的转变问题。而使用RestTemplate因为没有进行URL解码,导致服务端接收的参数不对(服务端不会对接收到的参数进行解码),这就是为什么错误信息是鉴权不通过。不得已只能变更请求方式,进行了一次上线。

<think>好的,用户之前问了关于Spring Cloud调用方式的问题,现在又接着问RPCHTTP请求的差别。看起来他可能在学习微服务架构中的服务通信机制。我需要先确认他对这两个概念的基础了解,然后给出清晰的区别适用场景。 首先,用户可能已经了解Spring Cloud中的HTTP调用方式,比如RestTemplateFeign,这些都是基于HTTP的。现在他想进一步了解RPCHTTP的区别,可能是为了在项目中选择合适的通信方式。 我需要解释RPCHTTP的基本概念,然后从协议、性能、使用场景、耦合性、可读性等方面进行比较。要注意要用太多专业术语,让用户容易理解。同时,结合他之前的Spring Cloud问题,可以举例说明哪些场景适合用HTTP,哪些适合RPC,比如微服务内部通信可能用RPC,而对外API用HTTP。 还要注意用户可能的误区,比如认为RPC一定比HTTP快,或者HTTP只能用于外部调用。需要指出实际情况中,HTTP/2gRPC等现代技术如何影响性能,以及具体的应用场景如何选择。 可能用户在选择服务调用方式时遇到了困惑,需要明确两者的优缺点,帮助他做出决策。因此,结构上要分点对比,清晰明了,最后给出总结性的建议,帮助用户根据实际需求选择。</think>以下是 **RPC 请求与 HTTP 请求**的核心区别适用场景的对比分析,从协议设计、性能、耦合性、使用场景等维度展开: --- ### **1. 核心概念** - **HTTP 请求**: 基于 HTTP 协议(应用层协议),遵循 **请求-响应模型**,通过 URL 定位资源,使用标准方法(GET/POST)操作数据。常用于浏览器与服务器、服务间 RESTful API 交互。 **示例**:`GET https://api.example.com/users/1`。 - **RPC 请求**: 基于 **远程过程调用协议**(如 gRPC、Dubbo),目标是让调用远程服务像调用本地函数一样简单。RPC 协议通常自定义传输格式(如二进制),强调低延迟高性能。 **示例**:`userService.getUser(1)`(本地方法调用形式,实际触发远程服务)。 --- ### **2. 核心区别对比** | **维度** | **HTTP 请求** | **RPC 请求** | |--------| | **协议设计** | 基于通用 HTTP 协议,标准化程度高(如 RESTful 风格)。 | 基于自定义协议(如 gRPC 的 HTTP/2 + Protobuf),通常更轻量、高效。 | | **数据传输格式** | 文本格式(JSON/XML),可读性强,但体积较大。 | 二进制格式(如 Protobuf、Thrift),压缩率高,序列化/反序列化速度快。 | | **性能** | 协议开销大(如 HTTP 头、文本解析),性能较低,适合对延迟敏感的场景。 | 协议开销小,性能高(尤其是 gRPC),适合高频调用或内部服务通信。 | | **耦合性** | 松耦合,调用方只需知道接口 URL 参数格式(如 OpenAPI 文档)。 | 紧耦合,调用方需依赖服务接口定义(如 Protobuf 的 `.proto` 文件或 Java 接口)。 | | **可读性** | 请求响应数据直观(JSON/XML),便于调试(如 Postman 直接测试)。 | 二进制数据直接阅读,需工具解析,调试复杂度较高。 | | **适用场景** | 跨语言、跨平台交互(如公开 API、前后端通信)。 | 内部服务间高性能通信(如微服务集群、数据中心内部调用)。 | | **典型框架** | Spring Cloud Feign、RestTemplate、Apache HttpClient。 | gRPC、Dubbo、Thrift、Spring Cloud OpenFeign(支持部分 RPC 特性)。 | --- ### **3. 技术细节对比** #### **(1)协议层** - **HTTP**: 基于 TCP 的应用层协议,每个请求需建立连接(HTTP/1.1 支持长连接),头部信息冗余(如 Cookie、User-Agent)。 - **RPC**: 可基于 TCP 或 HTTP/2(如 gRPC),支持多路复用、头部压缩等特性,减少网络开销。 #### **(2)序列化** - **HTTP**: 通常使用 JSON/XML 文本序列化,兼容性强,但解析效率低。 - **RPC**: 使用二进制序列化(如 Protobuf),压缩率高,解析速度快(性能可提升 5~10 倍)。 #### **(3)服务治理** - **HTTP**: 需额外组件实现负载均衡、熔断(如 Spring Cloud Gateway + Ribbon)。 - **RPC**: 框架通常内置服务发现、负载均衡、熔断等能力(如 Dubbo 的注册中心 + 集群容错)。 --- ### **4. 使用场景建议** - **选择 HTTP 请求**: - 需要对外提供公开 API(如第三方调用)。 - 跨语言、跨团队协作(JSON 是通用标准)。 - 对性能要求高,注重可读性调试便捷性。 - **选择 RPC 请求**: - 微服务内部高频调用(如订单服务调用库存服务)。 - 对延迟敏感(如游戏、实时通信)。 - 需要强类型接口约束高效序列化(如大数据传输)。 --- ### **5. 示例对比** #### **HTTP 请求(RESTful API)** ```java // 使用 Feign 调用 HTTP 接口 @FeignClient(name = "user-service") public interface UserApi { @GetMapping("/users/{id}") User ge
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值