#1 问题描述
在基于Spring Boot
的项目中实现了请求转发(使用 RestTemplate 的 exchange 方法)的功能,忽然在前端报net::ERR_CONTENT_DECODING_FAILED 200 (OK)
的错误,后端及上游系统日志均显示请求已完成。
#2 原因探寻
上述错误字面意思为内容解码失败
,就是说浏览器拿到后端数据后没办法正常解码。此时我们看看请求响应的编码
可以看到上游系统启用了响应压缩
,然后中转系统读取方式为:
restTemplate.exchange(entity, String::class.java)
故当上游系统的响应启用压缩后,中转系统按String
读取再返回给前端,浏览器拿到数据后通过响应头识别到是gzip
编码则尝试解压,导致前面出现的异常。
#3 修复
要修复其实也很简单,在中转系统中用字节数组
格式读取响应即可(兼容上游系统的各种格式的响应),完整代码如下:
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
}
}
使用示例
@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 {
//此处可以做一些后续操作
}
}