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 {
       	//此处可以做一些后续操作
    }
}
相关推荐
虹梦未来1 小时前
【开发心得】在SpringBoot体系中正确使用redisConfig
java·spring boot·spring
user_admin_god2 小时前
OpenCode入门到入坑
java·人工智能·spring boot·语言模型
地瓜伯伯4 小时前
SpringBoot项目整合Elasticsearch启动失败的常见错误总结
spring boot·elasticsearch·spring cloud
霸道流氓气质4 小时前
SpringBoot中集成LangChain4j+阿里百炼平台实现AI对话记忆功能、对话隔离、对话持久化到Redis功能
人工智能·spring boot·redis
人道领域6 小时前
【黑马点评日记02】Redis缓存优化:商户查询性能提升百倍
java·spring boot·spring·servlet·tomcat·intellij-idea
四斤年华6 小时前
关于SpringBoot在MultipartFile上java.nio.file.NoSuchFileException: /tmp/undertow
java·spring boot·nio
希望永不加班7 小时前
SpringBoot 中 AOP 实现接口限流
java·spring boot·后端·spring
Devin~Y7 小时前
互联网大厂Java面试:Spring Boot/Redis/Kafka/K8s 可观测 + RAG(向量检索/Agent)三轮追问实录
java·spring boot·redis·kafka·kubernetes·spring mvc·webflux
慕容卡卡7 小时前
SpringAI的那些事儿
java·人工智能·spring boot
tumeng07117 小时前
springboot与springcloud对应版本
java·spring boot·spring cloud