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 {
       	//此处可以做一些后续操作
    }
}
相关推荐
我爱娃哈哈3 分钟前
SpringBoot 实现 RSA+AES 自动接口解密
java·spring boot·后端
崎岖Qiu4 分钟前
SpringBoot:基于注解 @PostConstruct 和 ApplicationRunner 进行初始化的区别
java·spring boot·后端·spring·javaee
小张快跑。18 分钟前
【SpringBoot进阶指南(一)】SpringBoot整合MyBatis实战、Bean管理、自动配置原理、自定义starter
java·开发语言·spring boot
草履虫建模22 分钟前
A02 Maven 基础配置:本地仓库、镜像、项目编码与常见问题(IDEA 实战)
xml·java·spring boot·spring·maven·intellij-idea·idea
shejizuopin29 分钟前
基于Spring Boot的高校科研管理系统的设计与实现(毕业论文)
java·spring boot·vue·毕业设计·论文·毕业论文·高校科研管理系统的设计与实现
indexsunny30 分钟前
互联网大厂Java面试实战:Spring Boot微服务在电商场景中的应用
java·数据库·spring boot·redis·微服务·kafka·电商
哪里不会点哪里.37 分钟前
如何自定义一个 Spring Boot Starter?
java·spring boot·后端
不吃香菜学java43 分钟前
springboot左脚踩右脚螺旋升天系列-入门程序
java·spring boot·后端
悟能不能悟1 小时前
eclipse run springboot的application类,保存文件的路径会默认在哪里
java·spring boot·eclipse
没有bug.的程序员1 小时前
Spring Boot 性能优化:启动时间从 5s 到 1s 的全链路实战指南
java·spring boot·后端·spring·性能优化·全链路·启动时间