RestTemplate 请求转发异常 ERR_CONTENT_DECODING_FAILED 200 (OK)

#1 问题描述

在基于Spring Boot的项目中实现了请求转发(使用 RestTemplate 的 exchange 方法)的功能,忽然在前端报net::ERR_CONTENT_DECODING_FAILED 200 (OK)的错误,后端及上游系统日志均显示请求已完成。

#2 原因探寻

上述错误字面意思为内容解码失败,就是说浏览器拿到后端数据后没办法正常解码。此时我们看看请求响应的编码

可以看到上游系统启用了响应压缩,然后中转系统读取方式为:

kotlin 复制代码
restTemplate.exchange(entity, String::class.java)

故当上游系统的响应启用压缩后,中转系统按String读取再返回给前端,浏览器拿到数据后通过响应头识别到是gzip编码则尝试解压,导致前面出现的异常。

#3 修复

要修复其实也很简单,在中转系统中用字节数组格式读取响应即可(兼容上游系统的各种格式的响应),完整代码如下:

kotlin 复制代码
class ServiceRoute {
    val logger = LoggerFactory.getLogger(javaClass)

    val restTemplate = RestTemplate().also {  }

    fun redirect(request:HttpServletRequest, response:HttpServletResponse, targetUrl:String, extraHeaders: Map<String, String?>?=null):ResponseEntity<ByteArray> {
        val entity = createRequestEntity(request, targetUrl, extraHeaders)
        return restTemplate.exchange(entity, ByteArray::class.java)
    }

    @Throws(URISyntaxException::class, IOException::class)
    private fun createRequestEntity(request: HttpServletRequest, url: String, extraHeaders: Map<String, String?>?): RequestEntity<*> {
        val httpMethod = HttpMethod.valueOf(request.method)
        val headers = parseRequestHeader(request)
        extraHeaders?.forEach { (k, v) -> headers.add(k, v) }

        //将原始请求转换为字节数组
        val body = StreamUtils.copyToByteArray(request.inputStream)
        return RequestEntity<Any>(body, headers, httpMethod, URI(url))
    }

    /**
     * 复制原始请求的 header 信息
     */
    private fun parseRequestHeader(request: HttpServletRequest): MultiValueMap<String, String?> {
        val headers = HttpHeaders()
        val headerNames: List<String> = Collections.list(request.headerNames)
        for (headerName in headerNames) {
            val headerValues: List<String> = Collections.list(request.getHeaders(headerName))
            for (headerValue in headerValues) {
                headers.add(headerName, headerValue)
            }
        }
        return headers
    }
}

使用示例

kotlin 复制代码
@RequestMapping("route/**", name = "转发请求")
fun redirect(response:HttpServletResponse):ResponseEntity<*> {
    val path = request.servletPath.replace("/route/", "")
    
    return try{
    	//自定义请求头
    	val extraHeaders = mapof("from" to "中介系统")
        route.redirect( request, response, "http://localhost:8080/${path}", extraHeaders ).also {
            //此处可查看返回内容
        }
    }
    catch (e:Exception) {
        logger.error("[SERVICE-ROUTE] 转发失败", e)
        ResponseEntity(e.message, HttpStatus.INTERNAL_SERVER_ERROR)
    }
    finally {
       	//此处可以做一些后续操作
    }
}
相关推荐
14L7 小时前
互联网大厂Java面试:从Spring Cloud到Kafka的技术考察
spring boot·redis·spring cloud·kafka·jwt·oauth2·java面试
地藏Kelvin8 小时前
Spring Ai 从Demo到搭建套壳项目(二)实现deepseek+MCP client让高德生成昆明游玩4天攻略
人工智能·spring boot·后端
一个有女朋友的程序员8 小时前
Spring Boot 缓存注解详解:@Cacheable、@CachePut、@CacheEvict(超详细实战版)
spring boot·redis·缓存
wh_xia_jun8 小时前
在 Spring Boot 中使用 JSP
java·前端·spring boot
yuren_xia9 小时前
在Spring Boot中集成Redis进行缓存
spring boot·redis·缓存
yuren_xia9 小时前
Spring Boot + MyBatis 集成支付宝支付流程
spring boot·tomcat·mybatis
我爱Jack11 小时前
Spring Boot统一功能处理深度解析
java·spring boot·后端
RainbowJie112 小时前
Spring Boot 使用 SLF4J 实现控制台输出与分类日志文件管理
spring boot·后端·单元测试
面朝大海,春不暖,花不开12 小时前
Spring Boot MVC自动配置与Web应用开发详解
前端·spring boot·mvc
发愤图强的羔羊12 小时前
SpringBoot异步导出文件
spring boot·后端