Spring微服务间使用WebClient调用报400 Bad Request 直连下游正常
问题根因
- 核心是WebClient构建请求URI时的API误用:将带
http://协议、主机名的完整URL字符串传入了UriBuilder.path()方法。path()方法的设计用途是拼接相对路径片段,它不会自动把传入的字符串解析成包含协议、主机的绝对URI,只会把整段字符串当成普通路径片段处理,直接破坏了标准URI的结构(标准URI结构为协议://主机/路径?参数):原本应该放在URI主机位的ratings-data-service被错误放置到路径位置,最终引发连锁问题:- 负载均衡组件在把服务名
ratings-data-service解析为实际实例地址localhost:7654时,无法正确识别原URI的结构,也就没法同步更新请求的Host头字段,最终发出的请求里Host头仍残留服务名字符串。 - 下游ratings-data-service的Web服务器(Netty)收到请求时,会校验请求行与Host头的匹配性,发现Host头和当前服务的监听地址不匹配,直接返回400 Bad Request。
- 负载均衡组件在把服务名
- 直接访问接口可正常返回的原因:浏览器构造请求时会严格遵循URI规范,自动将Host头设置为访问的目标地址
localhost:7654,完全符合服务端校验规则,因此不会触发400错误。
修复方案
- 修正URI构建逻辑,不要把完整绝对URL传入
path()方法,可以参考以下两种正确写法重写业务方法:- 通过UriBuilder分步骤设置URI属性,同时将List类型的电影ID展开为可变参数传入
queryParam,避免把整个List对象当成单个参数值处理:fun findRatingByMovieId(movieIds: List<String>) : Flux<Rating> { return webClient.build() .get() .uri { uribuilder -> uribuilder.scheme("http") .host("ratings-data-service") .path("/ratings/list") .queryParam("movieIds", *movieIds.toTypedArray()) .build() } .retrieve() .bodyToFlux(Rating::class.java) } - 直接拼接完整URI字符串传入
uri()方法,避免UriBuilder的误用:fun findRatingByMovieId(movieIds: List<String>) : Flux<Rating> { val queryStr = movieIds.joinToString("&") { "movieIds=$it" } val targetUri = "http://ratings-data-service/ratings/list?$queryStr" return webClient.build() .get() .uri(targetUri) .retrieve() .bodyToFlux(Rating::class.java) }
- 通过UriBuilder分步骤设置URI属性,同时将List类型的电影ID展开为可变参数传入
内容的提问来源于stack exchange,提问作者Yasin Eraslan
相关产品推荐
相关产品推荐

