Spring Cloud Gateway:基于请求头重定向及代理静态资源异常问题
解决Spring Cloud Gateway代理Google时静态资源加载异常的问题
这个问题我之前碰到过类似的,核心原因是页面里的静态资源引用路径没有和代理路径同步,导致浏览器要么直接请求原域名(跨域或网络问题),要么请求你的服务器未代理的路径(404)。下面给你分析原因和具体的解决方案:
问题根源拆解
当你用ProxyExchange代理https://www.google.com后,返回的页面里的资源引用通常有两种情况:
- 绝对路径:比如
<img src="https://www.google.com/images/logo.png">,浏览器会直接请求Google的域名,可能因为跨域限制、网络防火墙等原因加载失败; - 根相对路径:比如
<img src="/images/logo.png">,浏览器会基于当前页面的URL(比如https://your-domain/proxgoo/)去请求https://your-domain/images/logo.png,而这个路径并没有被你的代理逻辑覆盖,所以返回404。
解决方案一:改用Spring Cloud Gateway内置路由配置(推荐)
Spring Cloud Gateway自带了专门的过滤器来处理代理场景的路径重写、响应体修改,比手动用ProxyExchange更健壮。用Kotlin配置路由的示例如下:
import org.springframework.cloud.gateway.route.RouteLocator import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder import org.springframework.cloud.gateway.filter.factory.RewriteLocationResponseHeaderFilter import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @Configuration class GatewayConfig { @Bean fun customRouteLocator(builder: RouteLocatorBuilder): RouteLocator { return builder.routes() .route("google-proxy") { r -> r.path("/proxgoo/**") .filters { f -> // 把/proxgoo/xxx重写为/xxx转发给Google f.rewritePath("/proxgoo/(?<segment>.*)", "/${'$'}segment") // 重写Location响应头,处理Google的重定向路径 .rewriteLocationResponseHeader( RewriteLocationResponseHeaderFilter.LocationRewriteMode.AS_IN_REQUEST, "Location", "", "/proxgoo/" ) // 修改响应体里的所有根相对路径,加上/proxgoo前缀 .modifyResponseBody<String, String> { _, body -> body.replace("""src="/""", """src="/proxgoo/""") .replace("""href="/""", """href="/proxgoo/""") .replace("""action="/""", """action="/proxgoo/""") } } .uri("https://www.google.com") } .build() } }
这个配置会自动处理:
- 路径转发:把
/proxgoo/xxx转发到https://www.google.com/xxx; - 重定向修正:如果Google返回重定向,自动把Location里的路径加上
/proxgoo前缀; - 静态资源路径替换:把页面里的所有根相对资源引用都改成代理路径,确保浏览器走你的代理请求资源。
解决方案二:手动修改ProxyExchange的响应(适合简单场景)
如果你坚持要用原来的ProxyExchange方式,需要手动修改响应体和响应头,把资源路径调整为代理路径:
import org.springframework.http.HttpHeaders import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RestController import org.springframework.web.servlet.mvc.method.annotation.ProxyExchange import java.net.URI @RestController class ProxyController { private val proxiedRemote = URI.create("https://www.google.com") @GetMapping("/proxgoo/**") fun proxyPath(proxy: ProxyExchange<*>): ResponseEntity<*> { val path = proxy.path("/proxgoo/") val originalResponse = proxy.uri("${proxiedRemote}/$path").get() // 1. 修改响应体里的资源路径 val modifiedBody = (originalResponse.body as? String)?.run { replace("""src="/""", """src="/proxgoo/""") .replace("""href="/""", """href="/proxgoo/""") .replace("""action="/""", """action="/proxgoo/""") // 额外处理绝对URL的情况,替换成代理路径 .replace(proxiedRemote.toString(), "/proxgoo") } ?: originalResponse.body // 2. 修改Location响应头,处理重定向 val modifiedHeaders = HttpHeaders(originalResponse.headers).apply { location?.let { location -> when { location.path.startsWith("/") -> { this.location = URI.create("/proxgoo${location.path}${location.query?.let { "?$it" } ?: ""}") } location.host == proxiedRemote.host -> { this.location = URI.create("/proxgoo${location.path}${location.query?.let { "?$it" } ?: ""}") } } } } return ResponseEntity.status(originalResponse.statusCode) .headers(modifiedHeaders) .body(modifiedBody) } }
额外注意事项
- Google的页面有很多动态加载的资源(比如JS动态生成的路径),上面的字符串替换可能无法覆盖所有情况,这种场景下推荐用Gateway的路由配置,或者考虑使用专门的HTML解析库(比如Jsoup)来更精准地修改资源路径;
- 如果遇到跨域问题,可以在Gateway里添加CORS过滤器,允许浏览器访问代理后的资源;
- 部分资源可能有Referer校验,需要在代理时传递正确的Referer头,Gateway的过滤器会自动处理这一点。
内容的提问来源于stack exchange,提问作者user5539487
相关产品推荐
相关产品推荐

