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 {
       	//此处可以做一些后续操作
    }
}
相关推荐
Yvemil719 分钟前
《开启微服务之旅:Spring Boot Web开发》(二)
前端·spring boot·微服务
计算机学长felix33 分钟前
基于SpringBoot的“旅游管理系统”的设计与实现(源码+数据库+文档+PPT)
spring boot·毕业设计
苹果醋31 小时前
SpringBoot快速入门
java·运维·spring boot·mysql·nginx
皓木.2 小时前
(自用)配置文件优先级、SpringBoot原理、Maven私服
java·spring boot·后端
i7i8i9com2 小时前
java 1.8+springboot文件上传+vue3+ts+antdv
java·spring boot·后端
寻找沙漠的人2 小时前
JavaEE 导读与环境配置
java·spring boot·java-ee
coding侠客3 小时前
Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘
java·spring boot·后端
武昌库里写JAVA4 小时前
使用React Strict DOM改善React生态系统
数据结构·vue.js·spring boot·算法·课程设计
嗨小陈4 小时前
旅游推荐系统设计与实现 计算机毕业设计 有源码 P10090
spring boot·课程设计·旅游·计算机毕业设计